Why use a wild card capture helper method?

后端 未结 4 1007
感动是毒
感动是毒 2020-12-31 07:24

Referring to : Wildcard Capture Helper Methods

It says to create a helper method to capture the wild card.

public void foo(List i) {
    fo         


        
相关标签:
4条回答
  • 2020-12-31 08:03

    You are correct that we don't have to use the wildcard version.

    It comes down to which API looks/feels "better", which is subjective

        void foo(List<?> i) 
    <T> void foo(List<T> i)
    

    I'll say the 1st version is better.

    If there are bounds

        void foo(List<? extends Number> i) 
    <T extends Number> void foo(List<T> i)
    

    The 1st version looks even more compact; the type information are all in one place.

    At this point of time, the wildcard version is the idiomatic way, and it's more familiar to programmers.

    There are a lot of wildcards in JDK method definitions, particularly after java8's introduction of lambda/Stream. They are very ugly, admittedly, because we don't have variance types. But think how much uglier it'll be if we expand all wildcards to type vars.

    0 讨论(0)
  • 2020-12-31 08:04

    The Java 14 Language Specification, Section 5.1.10 (PDF) devotes some paragraphs to why one would prefer providing the wildcard method publicly, while using the generic method privately. Specifically, they say (of the public generic method):

    This is undesirable, as it exposes implementation information to the caller.

    What do they mean by this? What exactly is getting exposed in one and not the other?

    Did you know you can pass type parameters directly to a method? If you have a static method <T> Foo<T> create() on a Foo class -- yes, this has been most useful to me for static factory methods -- then you can invoke it as Foo.<String>create(). You normally don't need -- or want -- to do this, since Java can sometimes infer those types from any provided arguments. But the fact remains that you can provide those types explicitly.

    So the generic <T> void foo(List<T> i) really takes two parameters at the language level: the element type of the list, and the list itself. We've modified the method contract just to save ourselves some time on the implementation side!

    It's easy to think that <?> is just shorthand for the more explicit generic syntax, but I think Java's notation actually obscures what's really going on here. Let's translate into the language of type theory for a moment:

    /*                 Java *//* Type theory      */
                 List<?>     ~~   ∃T. List<T>
        void foo(List<?> l)  ~~  (∃T. List<T>) -> ()
    <T> void foo(List<T> l)  ~~   ∀T.(List<T>  -> ()
    

    A type like List<?> is called an existential type. The ? means that there is some type that goes there, but we don't know what it is. On the type theory side, ∃T. means "there exists some T", which is essentially what I said in the previous sentence -- we've just given that type a name, even though we still don't know what it is.

    In type theory, functions have type A -> B, where A is the input type and B is the return type. (We write void as () for silly reasons.) Notice that on the second line, our input type is the same existential list we've been discussing.

    Something strange happens on the third line! On the Java side, it looks like we've simply named the wildcard (which isn't a bad intuition for it). On the type theory side we've said something _superficially very similar to the previous line: for any type of the caller's choice, we will accept a list of that type. (∀T. is, indeed, read as "for all T".) But the scope of T is now totally different -- the brackets have moved to include the output type! That's critical: we couldn't write something like <T> List<T> reverse(List<T> l) without that wider scope.

    But if we don't need that wider scope to describe the function's contract, then reducing the scope of our variables (yes, even type-level variables) makes it easier to reason about those variables. The existential form of the method makes it abundantly clear to the caller that the relevance of the list's element type extends no further than the list itself.

    0 讨论(0)
  • 2020-12-31 08:17

    In this particular case it's because the List.set(int, E) method requires the type to be the same as the type in the list.

    If you don't have the helper method, the compiler doesn't know if ? is the same for List<?> and the return from get(int) so you get a compiler error:

    The method set(int, capture#1-of ?) in the type List<capture#1-of ?> is not applicable for the arguments (int, capture#2-of ?)
    

    With the helper method, you are telling the compiler, the type is the same, I just don't know what the type is.

    So why have the non-helper method?

    Generics weren't introduced until Java 5 so there is a lot of code out there that predates generics. A pre-Java 5 List is now a List<?> so if you were trying to compile old code in a generic aware compiler, you would have to add these helper methods if you couldn't change the method signatures.

    0 讨论(0)
  • 2020-12-31 08:17

    I agree: Delete the helper method and type the public API. There's no reason not to, and every reason to.

    Just to summarise the need for the helper with the wildcard version: Although it's obvious to us as humans, the compiler doesn't know that the unknown type returned from l.get(0) is the same unknown type of the list itself. ie it doesn't factor in that the parameter of the set() call comes from the same list object as the target, so it must be a safe operation. It only notices that the type returned from get() is unknown and the type of the target list is unknown, and two unknowns are not guaranteed to be the same type.

    0 讨论(0)
提交回复
热议问题