Java Generics Puzzler, een klasse uitbreiden en jokertekens gebruiken

Ik loop hier al een tijdje tegenaan en dacht dat misschien een paar frisse ogen het probleem zouden zien; bedankt voor je tijd.

import java.util.*;
class Tbin<T> extends ArrayList<T> {}
class TbinList<T> extends ArrayList<Tbin<T>> {}
class Base {}
class Derived extends Base {}
public class Test {
  public static void main(String[] args) {
    ArrayList<Tbin<? extends Base>> test = new ArrayList<>();
    test.add(new Tbin<Derived>());
    TbinList<? extends Base> test2 = new TbinList<>();
    test2.add(new Tbin<Derived>());
  }
}

Java 8 gebruiken. Het lijkt mij dat de directe creatie van de container in testgelijk is aan de container in test2, maar de compiler zegt:

Test.java:15: error: no suitable method found for add(Tbin<Derived>)
    test2.add(new Tbin<Derived>());
         ^

Hoe schrijf ik Tbinen TbinListzodat de laatste regel acceptabel is?

Merk op dat ik eigenlijk getypte Tbins zal toevoegen, daarom heb ik Tbin<Derived>gespecificeerd in de laatste regel.


Antwoord 1, autoriteit 100%

Dit komt door de manier waarop conversie vastleggenwerkt:

Er bestaat een capture-conversie van een geparametriseerd type G<T1,...,Tn>naar een geparametriseerd type G<S1,...,Sn>, waarbij, voor 1 ≤ i ≤ n:

  • Als Tieen wildcard-argument is in de vorm ? extends Bi, dan is Sieen nieuwe variabele […].

Capture-conversie wordt niet recursief toegepast.

Let op het eindbit. Dus wat dit betekent is dat, gegeven een type als dit:

   Map<?, List<?>>
//      │  │    └ no capture (not applied recursively)
//      │  └ T2 is not a wildcard
//      └ T1 is a wildcard

Alleen ‘buiten’-jokertekens worden vastgelegd. Het jokerteken van de Map-sleutel wordt vastgelegd, maar het jokerteken van het List-element niet. Dit is de reden waarom we bijvoorbeeld wel kunnen toevoegen aan een List<List<?>>, maar niet aan een List<?>. De plaatsing van het jokerteken is waar het om gaat.

Als we dit overdragen naar TbinList, als we een ArrayList<Tbin<?>>hebben, bevindt het jokerteken zich op een plaats waar het niet wordt vastgelegd, maar als we een TbinList<?>hebben, staat het jokerteken op een plaats waar het wordt vastgelegd.

Zoals ik al zei in de opmerkingen, is een zeer interessante test deze:

ArrayList<Tbin<? extends Base>> test3 = new TbinList<>();

We krijgen deze foutmelding:

error: incompatible types: cannot infer type arguments for TbinList<>
    ArrayList<Tbin<? extends Base>> test3 = new TbinList<>();
                                                        ^
    reason: no instance(s) of type variable(s) T exist so that
            TbinList<T> conforms to ArrayList<Tbin<? extends Base>>

Dus er is geen manier om het te laten werken zoals het is. Een van de klassendeclaraties moet worden gewijzigd.


Bovendien, denk er zo over na.

Stel dat we hadden:

class Derived1 extends Base {}
class Derived2 extends Base {}

En aangezien een jokerteken subtypen toestaat, kunnen we dit doen:

TbinList<? extends Base> test4 = new TbinList<Derived1>();

Moeten we een Tbin<Derived2>kunnen toevoegen aan test4? Nee, dit zou hoopvervuiling zijn. We kunnen eindigen met Derived2s die rondzweven in een TbinList<Derived1>.


Antwoord 2, autoriteit 27%

De definitie van TbinListvervangen door

class TbinList<T> extends ArrayList<Tbin<? extends T>> {}

en definieer test2met

TbinList<Base> test2 = new TbinList<>();

in plaats daarvan zou het probleem worden opgelost.

Met jouw definitie krijg je een ArrayList<Tbin<T>>waarbij T een vaste klasse is die Baseuitbreidt.


Antwoord 3, autoriteit 8%

Je gebruikt een begrensd jokerteken (TbinList<? extends Base>> ...uit). Dit jokerteken zorgt ervoor dat u geen elementen aan de lijst kunt toevoegen. Als u meer informatie wilt, vindt u hier het gedeelte over Wildcardsin de documenten.


Antwoord 4, autoriteit 3%

U kunt de generieke typen als volgt definiëren:

class Tbin<T> extends ArrayList<T> {}
class TbinList<K, T extends Tbin<K>> extends ArrayList<T> {}

Dan zou u een instantie maken zoals:

TbinList<? extends Base, Tbin<? extends Base>> test2 = new TbinList<>();
test2.add(new Tbin<Derived>());

Antwoord 5, autoriteit 3%

OK, hier is het antwoord:

import java.util.*;
class Tbin<T> extends ArrayList<T> {}
class TbinList<T> extends ArrayList<Tbin<? extends T>> {}
class Base {}
class Derived extends Base {}
public class Test {
  public static void main(String[] args) {
    TbinList<Base> test3 = new TbinList<>();
    test3.add(new Tbin<Derived>());
  }
}

Zoals ik had verwacht, was het een beetje duidelijk toen ik het zag. Maar veel gesjouw om hier te komen. Java-generieken lijken eenvoudig als je alleen naar werkende code kijkt.

Bedankt, iedereen, om een ​​klankbord te zijn.


Antwoord 6, autoriteit 3%

u kunt geen objecten toevoegen aan TbinList<? extends Base>uit, het is niet gegarandeerd welke objecten u in de lijst invoegt. Het wordt verondersteld gegevens te lezen van test2wanneer u jokertekens extends

gebruikt

Als u heeft aangegeven als TbinList<? extends Base>uit, wat betekent dat je het een subklasse van de klasse Base of klasse Base zelf is, en wanneer je het initialiseert, gebruik je een andere diamant dan de concrete klassenaam, het maakt je test2niet duidelijk waardoor het moeilijker is om te zien welke objecten kunnen worden ingevoegd. Mijn suggestie is om zo’n verklaring te vermijden, het is gevaarlijk, het heeft misschien geen compileerfouten, maar het is vreselijke code, je zou iets kunnen toevoegen, maar je zou ook het VERKEERDE ding kunnen toevoegen dat je code zal breken.

Other episodes