Programmquelltext

Suppliers, Maps, Generics and Wildcards in Java 8: Mind the co-variance gap!


I need to unite map keys of dif­fe­rent Maps in Java 8. The­re are two important requirements:

  1. The maps do all have the same (gene­ri­fied) key type but may have total­ly dif­fe­rent value types.
  2. The maps are acces­sed through Suppliers. This allows late eva­lua­ti­on, i.e. the map is only eva­lua­ted in the method its­elf, not when the method is called.

Using the Stream API, I code this:

Now, let’s defi­ne some data:

…and final­ly try to unite the keys – also as a Supplier so that eva­lua­ti­on is delay­ed until the field is actual­ly accessed:

The pro­blem is: It does not work. Eclip­se says:

The method uniteKeys(Supplier<Map<Ref,?>>...) is not applicable for the arguments (Supplier<Map<Integer,String>>, Supplier<Map<Integer,Integer>>)

So, what’s the pro­blem here? I dig­ged through some parts of the inter­net and final­ly found a blog artic­le explai­ning the gene­ric wild­card syn­tax of Java and the seman­ti­cs. With the help of this artic­le, I could nail the issue down to a misun­derstan­ding of vari­ance inhe­ri­tance in Java’s gene­rics sys­tem on my behalf. Explai­ning co-vari­ance, the aut­hor Zhong Yu explains:

[W]e can­not use a Supplier<Integer> whe­re a Supplier<Number> is expec­ted. That is very coun­ter-intui­ti­ve. […]

Intui­tively co-vari­ant types are almost always used with upper-boun­ded wild­cards, par­ti­cu­lar­ly in public APIs. If you see a con­cre­te type Supplier<Something> in an API, it is very likely a mistake.

Ok, thank you, Zhong Yu. Having lear­ned this, I rede­sign my uniteKeys() method using expli­cit­ly declared co-vari­ance with upper-boun­ded wildcards:

Note that the only chan­ge to the first ver­si­on is the para­me­ter decla­ra­ti­on. The method imple­men­ta­ti­on its­elf is total­ly unch­an­ged. Result: Now it works flawlessly.

Even ten years after their intro­duc­tion to the lan­guage, Java gene­rics can be tri­cky. Nevert­hel­ess, it is important to under­stand them, as they have beco­me more important than ever in modern func­tion­al Java. The who­le thing here is about com­pi­la­ti­on, not about run­time beha­viour – remem­ber that all gene­ric infor­ma­ti­on is era­sed out of the .class files during compilation.

So, gene­rics are the­re to help the com­pi­ler ensu­re that type assign­ments are cor­rect at com­pi­le time. Using type infe­rence, it dedu­ces types from the code and checks that assign­ments are cor­rect regar­ding the types. And this can real­ly be tri­cky. Note that the first, wron­gly defi­ned uniteKeys() ver­si­on actual­ly can be cal­led like this:

If I have unders­tood it cor­rect­ly, this code „wraps” ano­ther Supplier around the actu­al mapXsupp and that Supplier infers the Maps values types to the wild­card ? of the uniteKeys() defi­ni­ti­on. Any­way, this ver­si­on is cle­ar­ly infe­ri­or com­pared to the cor­rect­ly defi­ned uniteKeys as it needs that addi­tio­nal (and total­ly use­l­ess) impli­cit Supplier.

Any­way, I think this is a real­ly nice exam­p­le of the com­ple­xi­ties of modern Java.

If someone wants to play with this, I add here a com­ple­te exam­p­le class with the code:

Have fun with it!

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert