Sup­pliers, Maps, Ge­ne­rics and Wild­cards in Ja­va 8: Mind the co-va­ri­an­ce gap!

I need to unite map keys of dif­fe­rent Maps in Ja­va 8. The­re are two im­portant re­qui­re­ments:

  1. The maps do all have the sa­me (ge­ne­ri­fied) key ty­pe but may have to­tal­ly dif­fe­rent va­lue ty­pes.
  2. The maps are ac­ces­sed through Sup­pliers. This al­lows la­te eva­lua­ti­on, i.e. the map is on­ly eva­lua­ted in the me­thod its­elf, not when the me­thod is cal­led.

Using the Stream API, I code this:

Now, let’s de­fi­ne so­me da­ta:

…and fi­nal­ly try to unite the keys – al­so as a Sup­plier so that eva­lua­ti­on is de­lay­ed un­til the field is ac­tual­ly ac­ces­sed:

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

The me­thod uniteKeys(Supplier<Map<Ref,?>>...) is not ap­p­li­ca­ble for the ar­gu­ments (Supplier<Map<Integer,String>>, Supplier<Map<Integer,Integer>>)

So, what’s the pro­blem he­re? I dig­ged through so­me parts of the in­ter­net and fi­nal­ly found a blog ar­ti­cle ex­p­lai­ning the ge­ne­ric wild­card syn­tax of Ja­va and the se­man­ti­cs. With the help of this ar­ti­cle, I could nail the is­sue down to a mi­sun­derstan­ding of va­ri­an­ce in­heri­tan­ce in Java’s ge­ne­rics sys­tem on my be­half. Ex­p­lai­ning co-va­ri­an­ce, the aut­hor Zhong Yu ex­p­lains:

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

In­tui­tively co-va­ri­ant ty­pes are al­most al­ways used with up­per-boun­ded wild­cards, par­ti­cu­lar­ly in pu­blic APIs. If you see a con­cre­te ty­pe Supplier<Something> in an API, it is very li­kely a mista­ke.

Ok, thank you, Zhong Yu. Ha­ving lear­ned this, I re­de­sign my unite­Keys() me­thod using ex­pli­cit­ly de­cla­red co-va­ri­an­ce with up­per-boun­ded wild­cards:

No­te that the on­ly chan­ge to the first ver­si­on is the pa­ra­me­ter de­cla­ra­ti­on. The me­thod im­ple­men­ta­ti­on its­elf is to­tal­ly un­ch­an­ged. Re­sult: Now it works flaw­less­ly.

Even ten ye­ars af­ter their in­tro­duc­tion to the lan­guage, Ja­va ge­ne­rics can be tri­cky. Ne­ver­theless, it is im­portant to un­der­stand them, as they have be­co­me mo­re im­portant than ever in mo­dern func­tio­nal Ja­va. The who­le thing he­re is about com­pi­la­ti­on, not about run­ti­me be­ha­viour – re­mem­ber that all ge­ne­ric in­for­ma­ti­on is era­sed out of the .class files du­ring com­pi­la­ti­on.

So, ge­ne­rics are the­re to help the com­pi­ler en­su­re that ty­pe as­sign­ments are cor­rect at com­pi­le time. Using ty­pe in­fe­rence, it de­du­ces ty­pes from the code and checks that as­sign­ments are cor­rect re­gar­ding the ty­pes. And this can re­al­ly be tri­cky. No­te that the first, wron­gly de­fi­ned unite­Keys() ver­si­on ac­tual­ly can be cal­led li­ke this:

If I have un­ders­tood it cor­rect­ly, this code „wraps“ ano­t­her Sup­plier around the ac­tu­al ma­pX­supp and that Sup­plier in­fers the Maps va­lues ty­pes to the wild­card ? of the unite­Keys() de­fi­ni­ti­on. Any­way, this ver­si­on is clear­ly in­fe­ri­or com­pa­red to the cor­rect­ly de­fi­ned unite­Keys as it needs that ad­di­tio­nal (and to­tal­ly useless) im­pli­cit Sup­plier.

Any­way, I think this is a re­al­ly ni­ce examp­le of the com­ple­xi­ties of mo­dern Ja­va.

If so­meo­ne wants to play with this, I add he­re a com­ple­te examp­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.