Effective Java notes (30) give priority to generic methods

        Just as classes can benefit from generics, so can methods. Static utility methods are particularly well-suited for genericization. All "algorithmic" methods in Collections (such as binarySearch and sort) are genericized.

        Writing generic methods is similar to writing generic types. For example, the following method, which returns the union of two collections:

public static Set union(Set s1, Set s2) {
    Set result = new HashSet(s1);
    result.addAll(s2);
    return result;
}

        This method compiles, but with two warnings:

        To fix these warnings and make the method type-safe, modify the method declaration to declare a type parameter (type parameter) representing the element type of the three collections (two parameters and a return value), and in the method Use type parameters in . A type parameter list that declares type parameters, between the method's modifiers and its return value. In this example, the type parameter list is <E> and the return type is Set<E>. The naming convention for type parameters is the same as for generic methods and for generics: 

public static <E> Set<E> union(Set<E> s1, Set<E> s2) {
    Set<E> result = new HashSet<>(s1);
    result.addAll(s2);
    return result;
}

        At least for simple generic methods, this is the case. The method now compiles without warnings, provides type safety, and is easier to use. Below is a simple program that implements the method. The program contains no conversions and compiles without errors or warnings:

public static void main(String[] args) {
    Set<String> guys = Set.of("Tom", "Dick", "Harry");
    Set<String> stooges = Set.of("Larry","Moe", "Curly");
    Set<String> af1Cio = union(guys, stooges); .
    System.out.println(af1Cio);
}

        When this program is run, [ Moe, Harry , Tom , Curly , Larry , Dick ] will be printed out. (The output order of elements is implementation-independent.)

        The limitation of the union method is that the types of the three collections (two input parameters and a return value) must be exactly the same. Methods can be made more flexible by using bounded wildcard types.

        Sometimes it may be necessary to create an object that is immutable but works for many different types. Since generics are implemented by erasure, you can use a single object for all required type parameters, but you need to write a static factory method that repeatedly dispatches objects for each required type parameter. This pattern is called a generic singleton factory and is often used for function objects such as Collections.reverse Order and sometimes for collections such as Collections.emptySet.

        Suppose you want to write an identity function dispatcher. Function.identity is provided in the class library, so you don't need to write it yourself, but it makes sense to write it yourself. It would be wasteful to recreate one every time it is needed because it is stateless. If Java generics were reified, each type would need an identity function, but when they are erased, only a generic singleton is needed. Take a look at the following examples:

private static UnaryOperator<Object> IDENTITY_FN = (t) -> t;

@SuppressWarnings ("unchecked")
public static <T> UnaryOperator<T> identi tyFunction() {
    return (UnaryOperator<T>) IDENTITY_FN;
}

        IDENTITY_FN is converted to ( UnaryFunction<T> ), producing an unchecked conversion warning because UnaryFunction<Object> is not a UnaryFunction<T> for every T. But the identity function is special: it returns the argument unmodified, so we know that using it as a Unary Function<T> is type-safe no matter what the value of T is. Therefore, we can safely suppress unchecked conversion warnings produced by this conversion. Once disabled, the code compiles without any errors or warnings.

        Below is a sample program that utilizes generic singletons as UnaryFunction<String> and UnaryFunction<Number>. As usual, it contains no conversions and compiles without errors or warnings:

public static void main(String[] args) {
    String[] strings = { "jute", "hemp", "nylon" };
    UnaryOperator<String> sameString = identityFunction();
    for (String s : strings)
        System.out.printIn(sameString.apply(s));
    
    Number[] numbers = { 1,2.0, 3L };
    UnaryOperator <Number> sameNumber = identityFunction();
    for (Number n : numbers)
        System.out.println(sameNumber . apply(n)) ;
}

        Although relatively rare, it is permitted to restrict a type parameter by an expression that includes the type parameter itself. This is the recursive type bound (recursive type bound). The most common use of recursive type restrictions has to do with the Comparable interface, which defines the natural ordering of types. The content of this interface is as follows:

public interface Comparable<T> {
    int compareTo(T o);
}

        The type parameter T defines the type that can be compared with elements of types that implement Cornparable<T>. In fact, almost all types can only be compared with elements of their own type. For example, String implements Comparable<String>, Integer implements Comparable<Integer>, and so on.

        There are many methods that take a list of elements implementing the Comparable interface, in order to sort the list and search in it, calculate its minimum or maximum value, etc. To accomplish any of these operations, each element in the list is required to be comparable to every other element in the list, in other words, the elements of the list can be compared with each other (mutually comparable). Here's an example of how to express this constraint:

public static <E extends Comparable<E>> E max(Col1ection<E> C) ;

        The type constraint <E extends Cornparable<E>>, can be read as "for every type E that can be compared with itself", which is more or less consistent with the concept of comparability.

        The following method carries the above declaration. It computes the maximum value of the list according to the natural order of the elements, and compiles without errors or warnings:

pub1ic static <E extends Comparable<E>> E max(Collection<E> c) {
    if(c.isEmpty())
        throw new IllegalArgumentException("Empty collection");
    E result = null;
    for(E e:c)
        if (result == null || e.compareTo(result) > 0)
    result = Objects.requireNonNull(e);
    return result;
}

        Note that this method throws an IllegalArgumentException if the list is empty. A better alternative is to return an Optional<E>.

        Recursive type restrictions can be much more complicated than this, but fortunately, this doesn't happen very often. If you understand this idiom with its wildcard variant, and the simulated selιtype idiom, you will be able to deal with many of the recursive type restrictions you encounter in practice.

        All in all, generic methods, like generics, are safer and easier to use than methods that require clients to convert input parameters and return values. Just like types, you should ensure that methods can be used without conversion, which usually means making them generic. And just like types, existing methods should also be generic to make it easier for new users without breaking existing clients.

Guess you like

Origin blog.csdn.net/java_faep/article/details/132180217