Java thenComparing wildcard signature

前端 未结 3 1795
广开言路
广开言路 2021-02-05 18:48

Why does the declaration look like this:

default > Comparator thenComparing(
            Function

        
3条回答
  •  刺人心
    刺人心 (楼主)
    2021-02-05 19:17

    TL;DR:

    Comparator.thenComparing(Function< ? super T, ? extends U > keyExtractor) (the method your question specifically asks about) might be declared that way as an idiomatic/house coding convention thing that the JDK development team is mandated to follow for reasons of consistency throughout the API.


    The long-winded version

    …But I don't get this part: Function

    That part is placing a constraint on the specific type that the Function must return. It sounds like you got that part down already though.

    The U the Function returns is not just any old U, however. It must have the specific properties (a.k.a „bounds“) declared in the method's parameter section: >.

    …Why not just have: Function

    To put it as simply as I can (because I only think of it simply; versus formally): The reason is because U is not the same type as ? extends U.

    Changing Comparable< ? super U > to List< ? super U > and Comparator< T > to Set< T > might make your quandary easier to reason about…

    default < U extends List< ? super U > > Set< T > thenComparing(
        Function< ? super T, ? extends U > keyExtractor ) {
            
        T input = …;
            
        /* Intuitively, you'd think this would be compliant; it's not! */
        /* List< ? extends U > wtf = keyExtractor.apply( input ); */
          
        /* This doesn't comply to „U extends List< ? super U >“ either */
        /* ArrayList< ? super U > key = keyExtractor.apply( input ); */
            
        /* This is compliant because key is a „List extends List< ? super U >“
         * like the method declaration requires of U 
         */
        List< ? super U > key = keyExtractor.apply( input );
            
        /* This is compliant because List< E > is a subtype of Collection< E > */
        Collection< ? super U > superKey = key;
            
        …
    }
    

    Can't the U just parameterize to whatever the keyExtractor returns, and still extend Comparable all the same?…

    I have established experimentally that Function< ? super T, ? extends U > keyExtractor could indeed be refactored to the the more restrictive Function< ? super T, U > keyExtractor and still compile and run perfectly fine. For example, comment/uncomment the /*? extends*/ on line 27 of my experimental UnboundedComparator to observe that all of these calls succeed either way…

    …
    Function< Object, A > aExtractor = ( obj )-> new B( );
    Function< Object, B > bExtractor = ( obj )-> new B( ) ;
    Function< Object, C > cExtractor = ( obj )-> new C( ) ;
            
    UnboundedComparator.< Object, A >comparing( aExtractor ).thenComparing( bExtractor );
    UnboundedComparator.< Object, A >comparing( bExtractor ).thenComparing( aExtractor );
    UnboundedComparator.< Object, A >comparing( bExtractor ).thenComparing( bExtractor );
    UnboundedComparator.< Object, B >comparing( bExtractor ).thenComparing( bExtractor );
    UnboundedComparator.< Object, B >comparing( bExtractor ).thenComparing( aExtractor );
    UnboundedComparator.< Object, B >comparing( bExtractor ).thenComparing( cExtractor );
    …
    

    Technically, you could do the equivalent debounding in the real code. From the simple experimentation I've done — on thenComparing() specifically, since that's what your question asks about — I could not find any practical reason to prefer ? extends U over U.

    But, of course, I have not exhaustively tested every use case for the method with and without the bounded ? .

    I would be surprised if the developers of the JDK haven't exhaustively tested it though.

    My experimentation — limited, I admit — convinced me that Comparator.thenComparing(Function< ? super T, ? extends U > keyExtractor) might be declared that way for no other reason than as an idiomatic/house coding convention thing that the JDK development team follows.

    Looking at the code base of the JDK it's not unreasonable to presume that somebody somewhere has decreed: «Wherever there's a Function< T, R > the T must have a lower bound (a consumer/you input something) and the R must have an upper bound (a producer/you get something returned to you)».

    For obvious reasons though, U is not the same as ? extends U. So the former should not be expected to be substitutable for the latter.

    Applying Occam's razor: It's simpler to expect that the exhaustive testing the implementers of the JDK have done has established that the U -upper bounded wildcard is necessary to cover a wider number of use cases.

提交回复
热议问题