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­port­ant re­qui­re­ments:

  1. The maps do all ha­ve the sa­me (ge­ne­ri­fied) key ty­pe but may ha­ve 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­lica­ble for the ar­gu­ments (Supplier<Map<Integer,String>>, Supplier<Map<Integer,Integer>>)

So, what’s the pro­blem here? I dig­ged through so­me parts of the in­ter­net and fi­nal­ly found a blog ar­ti­cle ex­plai­ning the ge­ne­ric wild­card syn­tax of Ja­va and the se­man­ti­cs. Wi­th 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­t­an­ce in Java’s ge­ne­rics sys­tem on my be­half. Ex­plai­ning co-va­ri­an­ce, the aut­hor Zhong Yu ex­plains:

[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­tive­ly co-va­ri­ant ty­pes are al­most al­ways used wi­th 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 mis­ta­ke.

Ok, thank you, Zhong Yu. Ha­ving lear­ned this, I re­de­si­gn my unite­Keys() me­thod using ex­pli­cit­ly de­cla­red co-va­ri­an­ce wi­th 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­gua­ge, Ja­va ge­ne­rics can be tri­cky. Ne­ver­the­l­ess, it is im­port­ant to un­der­stand them, as they ha­ve be­co­me mo­re im­port­ant than ever in mo­dern func­tio­nal Ja­va. The who­le thing here 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­si­gn­ments are cor­rect at com­pi­le ti­me. Using ty­pe in­fe­ren­ce, it de­du­ces ty­pes from the code and checks that as­si­gn­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 ha­ve un­ders­tood it cor­rect­ly, this code „wraps“ ano­ther Sup­plier around the ac­tual 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­rior com­pa­red to the cor­rect­ly de­fi­ned unite­Keys as it nee­ds that ad­di­tio­nal (and to­tal­ly use­l­ess) im­pli­cit Sup­plier.

Any­way, I think this is a re­al­ly nice ex­am­ple of the com­ple­xi­ties of mo­dern Ja­va.

If so­meo­ne wants to play wi­th this, I add here a com­ple­te ex­am­ple class wi­th the code:

Ha­ve fun wi­th it!

Schreibe einen Kommentar

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