Toon posts:

[Java] Generics vraag

Pagina: 1
Acties:
  • 128 views sinds 30-01-2008
  • Reageer

Verwijderd

Topicstarter
Hoi,

Ik ben stilletjesaan begonnen met het leren van Java 5.0, en vooral generics wanneer ik bepaalde codes bekijk zie ik bvb in klasse arraylist volgende staan

public <T> T[] toArray(T[] a)

nu ik begrijp dat een array T[] wordt weergeleverd, mijn vraag is nu waarom staat voor die T[] de <T> als je sommige methoden ziet waarin generics worden gebruikt zie je dit echter niet bvb

public boolean addAll(Collection<? extends E> c)

ik zou dan denken dat je hier dan voor boolean zou zetten <? extends E>, op de site van sun zie ik hier ook niet direct iets over staan

hopelijk kan hier me iemand in wegwijs maken

alvast dank

  • JaWi
  • Registratie: Maart 2003
  • Laatst online: 14-01 21:58

JaWi

maak het maar stuk hoor...

Die <T> voor de T[] geeft aan dat je op het moment van aanroep nog een generics-declaratie kunt doen. Zo kun je bijv.:
Java:
1
String[] stringArray = myInstance.<String>toArray(new String[]{})

doen, zonder dat de gehele klasse waarvan myInstance een instatie is als een generic te declareren (zoals bijv. de klasses in de Collections-framework).

Anders gezegd: je kunt dus generics op twee niveau's declareren: op klasse-niveau, waarbij het voor die hele klasse geldt, en op methode-niveau, waarbij het alleen voor die betreffende methode geldt.

edit:
nog 'n kleine verduidelijking erbij gezet...

[ Voor 35% gewijzigd door JaWi op 18-08-2005 12:54 ]

Statistics are like bikinis. What they reveal is suggestive, but what they hide is vital.


  • .oisyn
  • Registratie: September 2000
  • Laatst online: 21:33

.oisyn

Moderator Devschuur®

Demotivational Speaker

Nou ken ik helemaal niets van java generics (maar het concept wel enigszins), maar het lijkt me toch duidelijk: bij die toArray functie heb je een returntype nodig, en dus een identifier die dat type specificeerd. Bij de addAll functie heb je een parameter van het type Collection<X>, waarbij je iig specificeert dat X E moet extenden, maar verder niet geinteresseerd bent in het daadwerkelijke type van X. Als je in de functie nou nog wel iets met die X wil doen, of bijv. in de parameters die erna komen, dan kom je niet weg met een vraagteken.

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


Verwijderd

Als je toch over generics aan het leren bent is het mischien wel interesant om te weten dat generics zelf geen echte nieuwe types zijn voor de VM, maar dat het een mechanisme is voor de compiler om automatisch casts toe te voegen waar nodig en type safeness warnings te geven. Het is eigenlijk een soort meta data die de compiler precies genoeg info geeft om dit te kunnen doen. Wat dat betreft had het waarschijnlijk ook met annotations gekunt, maar nu geeft het een meer first-class-citizen gevoel.

Verwijderd

Topicstarter
JaWi schreef op donderdag 18 augustus 2005 @ 12:47:
Die <T> voor de T[] geeft aan dat je op het moment van aanroep nog een generics-declaratie kunt doen. Zo kun je bijv.:
Java:
1
String[] stringArray = myInstance.<String>toArray(new String[]{})

doen, zonder dat de gehele klasse waarvan myInstance een instatie is als een generic te declareren (zoals bijv. de klasses in de Collections-framework).

Anders gezegd: je kunt dus generics op twee niveau's declareren: op klasse-niveau, waarbij het voor die hele klasse geldt, en op methode-niveau, waarbij het alleen voor die betreffende methode geldt.

edit:
nog 'n kleine verduidelijking erbij gezet...
ok maar ik zie niet in waarom je die declaratie daar nog moet doen want je heeft daar toch al type string mee in de parameterlijst

ook vind ik raar dat dit zo staat bij arraylist want die is toch al generisch dus is het raar dat je dat type dan nog moet meegeven

[ Voor 9% gewijzigd door Verwijderd op 18-08-2005 13:57 ]


  • JaWi
  • Registratie: Maart 2003
  • Laatst online: 14-01 21:58

JaWi

maak het maar stuk hoor...

Verwijderd schreef op donderdag 18 augustus 2005 @ 13:23:
[...]
ok maar ik zie niet in waarom je die declaratie daar nog moet doen want je heeft daar toch al type string mee in de parameterlijst

ook vind ik raar dat dit zo staat bij arraylist want die is toch al generisch dus is het raar dat je dat type dan nog moet meegeven
Dat laatste heeft meer te maken met het feit dat Sun zoveel mogelijk hun API backwards compatible wil houden. In Java 1.4.2 bijv, is op de List-interface de methode toArray(Object[]) gedefinieerd. Nadat de compiler z'n werk gedaan heeft, zal de bovenstaande generics-variant dezelfde dezelfde functie-call opleveren, waardoor de oorspronkelijke toArray(Object[]) methode aangeroepen wordt. Zoals Henk_de_Man al zei: generics zijn een compile-time feature en geen taal-feature, zoals bijv. bij C++. Samen met de backward compatibility-eis van Sun levert dit een nogal aparte manier van generics gebruik op voor de JFC...

Statistics are like bikinis. What they reveal is suggestive, but what they hide is vital.


  • .oisyn
  • Registratie: September 2000
  • Laatst online: 21:33

.oisyn

Moderator Devschuur®

Demotivational Speaker

JaWi schreef op donderdag 18 augustus 2005 @ 14:31:
Zoals Henk_de_Man al zei: generics zijn een compile-time feature en geen taal-feature, zoals bijv. bij C++.
Je bedoelt platformfeature ipv taalfeature, het is namelijk wel degelijk een feature van de taal. En ook totaal niet te vergelijken met C++ templates, wat weer heel anders werkt.

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


Verwijderd

.oisyn schreef op donderdag 18 augustus 2005 @ 14:35:
[...]

Je bedoelt platformfeature ipv taalfeature, het is namelijk wel degelijk een feature van de taal. En ook totaal niet te vergelijken met C++ templates, wat weer heel anders werkt.
Het is natuurlijk een taal feature omdat er simpelweg speciale syntax voor in de taal zit. De -uitwerking- van generics worden als ik het 100% goed heb alleen maar door de compiler gedaan. Daarom kun je ook generic classes naar Java 1.4 class format compileren. Eenmaal als class file zijnde is alle generic information weg en is het verschil met een normale class niet meer aanwezig.

M.a.w.

ArrayList<Integer> en ArrayList<String> levert niet een nieuw type op; er wordt nergens een class file hiervoor aangemaakt, maar beide zullen objecten gebruiken van de eenmalig in het systeem aanwezige ArrayList.class.

De kenners zullen het er niet helemaal mee eens zijn, want wat ik nu zeg is niet 100% correct, maar voor het begrip kun je C++ template's zien als een soort advanced macro die class definities voor jou genereerd. Als C++ .class files zou hebben, dan zou je een ArrayList_Integer.class en een ArrayList_String.class zien na compilatie van bovenstaande dingen.

Verwijderd

Topicstarter
sorry maar ik zie nog altijd het nut niet in van die <T> voor T[] te zetten je heeft in je methode toch al het type mee

  • mbravenboer
  • Registratie: Januari 2000
  • Laatst online: 06-11-2025
Er is duidelijk veel bekend over de manier waarop generics gecompileerd worden, maar toch heb ik nog niemand met een echt goede uitleg gezien van het originele probleem. Ik zal een poging wagen ...

Allereest de betekenis van een <T> voor methods. Net als classes kunnen methoden type parameters hebben. De methode is dan geparameterizeerd met een type. In dit geval, heeft de methode toArray 1 type parameter: T.

In tegenstelling tot geparameterizeerde klassen, geef je de waarde van de type parameter meestal niet op bij een methode aanroep. De parameter kan namelijk meestal geinfereerd worden uit het type van de argumenten van de methode aanroep. In dit geval, kan de waarde van T afgeleid worden uit de String[] die wordt meegegeven. Je hoeft de waarde van T dus ook niet op te geven in de aanroep: list.toArray(new String[ ... ]) volstaat.

Dan nu, waarom heeft toArray een type parameter? Het idee van deze toArray variant is dat je zelf het type van de elementen van de array kan bepalen. Dit type kan belangrijk zijn als je er ook nog andere elementen in wilt stoppen. Als je bijvoorbeeld voor een ArrayList<String> alleen maar een String[] zou kunnen krijgen van z'n inhoud, dan kan je er onmogelijk Objecten of, iets specifieker, CharSequences instoppen.

Er is met het signatuur van deze methode dus eigenlijk niks mis, en er zijn ook geen serieuze concessies gedaan ivm backwards compatiblity (of zoals Sun het noemt: migration compatibility).

Toch is er misschien 1 punt van kritiek mogelijk. Je kan namelijk een willekeurige array meegeven aan deze methode. Je kan dus ook een array meegeven met een type wat specifieker is dan het type van ArrayList, waardoor er bij het vullen runtime foutmeldingen kunnen komen. Er is hier een oplossing voor: de type parameter moet een super bound hebben: T moet een supertype zijn van E. Helaas, is een super bound alleen toegestaan voor wildcards. Type parameters (zoals T in deze methode) kunnen dus geen super bounds hebben.

Als deze methode een List argument zou hebben (wat verder natuurlijk zinloos is), zou hij waarschijnlijk het volgende signatuur hebben:
Java:
1
  void toList(List<? super E>)

Dit werkt echter niet voor arrays: het element type van een array mag geen wildcard zijn. Ook kan de type parameter T geen super bound hebben, dus er is geen manier om deze type voorwaarde te declareren.

Een tijdje geleden heb ik op m'n blog compact proberen uit te leggen wat het nut van wildcards is:
Generics: The Importance of Wildcards

[ Voor 11% gewijzigd door mbravenboer op 18-08-2005 16:31 ]

Blog, Stratego/XT: Program Transformation, SDF: Syntax Definition, Nix: Software Deployment


  • mbravenboer
  • Registratie: Januari 2000
  • Laatst online: 06-11-2025
Ik had je vraag nog iets beter moeten lezen, dan had ik nog iets beter kunnen antwoorden ...
TomVdH:
Als je sommige methoden ziet waarin generics worden gebruikt zie je dit echter niet bvb

public boolean addAll(Collection<? extends E> c)

ik zou dan denken dat je hier dan voor boolean zou zetten <? extends E>, op de site van sun zie ik hier ook niet direct iets over staan
Deze methode gebruikt dus wildcards, maar heeft geen type parameters. De methode neemt slechts een Collection argument die geparameterizeerd moet zijn met een type wat specifieker of gelijk aan E is. Dit onbekende type (het ?) hoeft dankzij wildcards niet opgenomen te worden in het signatuur.

In sommige situaties is een wildcard inderdaad suiker voor het invoeren van een parameter. De methode had ook zo opgeschreven kunnen zijn:
Java:
1
public <T> boolean addAll(Collection<T extends E> c)

[ Voor 3% gewijzigd door mbravenboer op 18-08-2005 16:31 ]

Blog, Stratego/XT: Program Transformation, SDF: Syntax Definition, Nix: Software Deployment


Verwijderd

Topicstarter
mbravenboer schreef op donderdag 18 augustus 2005 @ 15:42:
Er is duidelijk veel bekend over de manier waarop generics gecompileerd worden, maar toch heb ik nog niemand met een echt goede uitleg gezien van het originele probleem. Ik zal een poging wagen ...

Allereest de betekenis van een <T> voor methods. Net als classes kunnen methoden type parameters hebben. De methode is dan geparameterizeerd met een type. In dit geval, heeft de methode toArray 1 type parameter: T.

In tegenstelling tot geparameterizeerde klassen, geef je de waarde van de type parameter meestal niet op bij een methode aanroep. De parameter kan namelijk meestal geinfereerd worden uit het type van de argumenten van de methode aanroep. In dit geval, kan de waarde van T afgeleid worden uit de String[] die wordt meegegeven. Je hoeft de waarde van T dus ook niet op te geven in de aanroep: list.toArray(new String[ ... ]) volstaat.


Dan nu, waarom heeft toArray een type parameter? Het idee van deze toArray variant is dat je zelf het type van de elementen van de array kan bepalen. Dit type kan belangrijk zijn als je er ook nog andere elementen in wilt stoppen. Als je bijvoorbeeld voor een ArrayList<String> alleen maar een String[] zou kunnen krijgen van z'n inhoud, dan kan je er onmogelijk Objecten of, iets specifieker, CharSequences instoppen.

Er is met het signatuur van deze methode dus eigenlijk niks mis, en er zijn ook geen serieuze concessies gedaan ivm backwards compatiblity (of zoals Sun het noemt: migration compatibility).

Toch is er misschien 1 punt van kritiek mogelijk. Je kan namelijk een willekeurige array meegeven aan deze methode. Je kan dus ook een array meegeven met een type wat specifieker is dan het type van ArrayList, waardoor er bij het vullen runtime foutmeldingen kunnen komen. Er is hier een oplossing voor: de type parameter moet een super bound hebben: T moet een supertype zijn van E. Helaas, is een super bound alleen toegestaan voor wildcards. Type parameters (zoals T in deze methode) kunnen dus geen super bounds hebben.

Als deze methode een List argument zou hebben (wat verder natuurlijk zinloos is), zou hij waarschijnlijk het volgende signatuur hebben:
code:
1
  void toList(List<? super E>)

Dit werkt echter niet voor arrays: het element type van een array mag geen wildcard zijn. Ook kan de type parameter T geen super bound hebben, dus er is geen manier om deze type voorwaarde te declareren.

Een tijdje geleden heb ik op m'n blog compact proberen uit te leggen wat het nut van wildcards is:
Generics: The Importance of Wildcards
maar ik dacht dat als je een generisch type gebruikte je er geen subklassen ofzo kon insteken of andere klassen omdat dit een compile error zou geven
althans zo heb ik het toch willen verstaan in de tutorial

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 21:33

.oisyn

Moderator Devschuur®

Demotivational Speaker

Verwijderd schreef op donderdag 18 augustus 2005 @ 16:04:
maar ik dacht dat als je een generisch type gebruikte je er geen subklassen ofzo kon insteken of andere klassen omdat dit een compile error zou geven
Natuurlijk kun je er subclasses in steken. Als je een array van Objecten hebt mag je er toch ook een String in opslaan? Een String is immers een Object. Vandaar dat de originele collections altijd werkte met Objecten; dan kun je er álles instoppen, met het nadeel dat er ook altijd Objecten uitkomen, ook al sla je er alléén Strings in op. Je moet dus casten naar een String elke keer als je een String object uit een collection haalt.

Generics lossen dit op, je vertelt de compiler dat je er alleen maar bepaalde types (of subclasses daarvan!) in stopt, zodat je niet zelf hoeft te casten als je ze er weer uithaalt; de compiler garandeerd namelijk dat je er alleen objecten van een bepaald type in mag stoppen. Doe je dat niet (bijvoorbeeld een Double ipv een String) dan krijg je een compile-error.

Om de addAll(Collection<? extends E> c) te verklaren, je hebt hier te maken met een bepaalde Collection met een generic type E. Alle objecten in de collection zijn dus minstens van het type E. Als E hier Object was geweest, dan had je de collectie natuurlijk mogen aanvullen met een collection waar allemaal Strings in staan (een Collection<String> dus). Aangezien String extends Object, is de methodeaanroep met een Collection<String> volledig legaal.

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


  • mbravenboer
  • Registratie: Januari 2000
  • Laatst online: 06-11-2025
TomVdH: maar ik dacht dat als je een generisch type gebruikte je er geen subklassen ofzo kon insteken of andere klassen omdat dit een compile error zou geven
althans zo heb ik het toch willen verstaan in de tutorial
Waar jij op doelt is de invariantie van geparameterizeerde typen. Inderdaad is het zo, dat je als er een List<String> wordt verwacht, je geen List<CharSequence> kan meegeven of andersom.

Voor de elementen van de lijst is dit echter geen probleem: je kan gerust een String toevoegen aan een List<CharSequence>.

Daarom moet een signatuur van een generieke methode op specifiekere typen voorbereid zijn, als dit althans nuttig is voor de betreffende methode. Voorbeeld:
Java:
1
2
3
public class Foo<E> {
  public void addAll(Collection<E> c)
}

Als je nu een Collection aan addAll geeft, moet de parameter altijd precies hetzelfde type zijn als de waarde van E. Dit kan je versoepelen door de methode allAll generiek te maken: hij werkt dan op alle Collection<T> waarbij T een subtype is van E.
Java:
1
2
3
public class Foo<E> {
  public <T extends E> void addAll(Collection<T> c)
}


Een alternatief signatuur met wildcards is:
Java:
1
2
3
public class Foo<E> {
  public void addAll(Collection<? extends E> c)
}

Je weet bij dit signatuur in de methode addAll zelf echter niet wat de concrete parameter van Collection is (bij de vorige was dit T). Hierdoor kan je minder met deze Collection doen.

[ Voor 9% gewijzigd door mbravenboer op 18-08-2005 16:31 ]

Blog, Stratego/XT: Program Transformation, SDF: Syntax Definition, Nix: Software Deployment


  • mbravenboer
  • Registratie: Januari 2000
  • Laatst online: 06-11-2025
Nog even wat voorbeelden waarom eigenlijk die super bound nodig zou moeten zijn. Ik doe dit eerst met een List, omdat arrays covariant zijn, dus klaagt de compiler niet. At runtime klaagt hij echter wel en dat komt later ...

Poging 1:
Java:
1
2
3
4
5
6
7
8
9
class MyPair<E> {
  private E e1;
  private E e2;

  public void toList(List<E> list) {
    list.add(e1);
    list.add(e2);
  }
}

Dit compileert en werkt, maar echt goed is het niet: je moet nu altijd een List meegegeven met precies dezelfde parameter als MyPair.

Als we algemere lijsten willen toestaan, kunnen we dit proberen:
Java:
1
2
3
4
5
6
7
8
9
class MyPair<E> {
  private E e1;
  private E e2;

  public <T> void toList(List<T> list) {
    list.add(e1);
    list.add(e2);
  }
}

Maar helaas, dit compileert niet omdat je nu elke willekeurige lijst kan meegeven en er is geen garantie dat daar een E in mag. Je wilt dus een super bound zetten op de parameter T:

Java:
1
2
3
4
5
6
7
8
9
class MyPair<E> {
  private E e1;
  private E e2;

  public <T super E> void toList(List<T> list) {
    list.add(e1);
    list.add(e2);
  }
}

Maar helaas, dit compileert niet: type parameters kunnen geen super bounds hebben.

Met wildcards kan dit echter wel:
Java:
1
2
3
4
5
6
7
8
9
class MyPair<E> {
  private E e1;
  private E e2;

  public void toList(List<? super E> list) {
    list.add(e1);
    list.add(e2);
  }
}

Dit compileert en is precies wat we wilden :) .

[ Voor 4% gewijzigd door mbravenboer op 18-08-2005 16:42 ]

Blog, Stratego/XT: Program Transformation, SDF: Syntax Definition, Nix: Software Deployment


  • mbravenboer
  • Registratie: Januari 2000
  • Laatst online: 06-11-2025
Nu dan nog een voorbeeld waarom de toArray methode van ArrayList eigenlijk een super bound op T nodig heeft:

Java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import java.util.ArrayList;
import java.util.List;

class Foo {
  public static void main(String [] ps) {
    List<CharSequence> list = new ArrayList<CharSequence>();
    list.add("bar");
    list.add(new StringBuffer("foo"));


    String[] strings = new String[10];
    list.toArray(strings);
  }
}


Run:
code:
1
2
3
4
Exception in thread "main" java.lang.ArrayStoreException
        at java.lang.System.arraycopy(Native Method)
        at java.util.ArrayList.toArray(ArrayList.java:305)
        at Foo.main(Foo.java:12)

Deze fout was al at compile-time gesignaleerd als er een super bound op de T parameter had gezeten.

Veel van het commentaar op generics gaat trouwens over de interactie tussen generics en arrays. Ook de laatste 'blog-storm' (aangewakkerd door Ken Arnold) ging feitelijk over de brakheid van arrays en niet over generics. Peter Ahe (javac ontwikkelaar bij Sun) vatte dit mooi samen op z'n blog: "Ken and others should consider arrays harmful, not generics.".

[ Voor 23% gewijzigd door mbravenboer op 18-08-2005 17:13 ]

Blog, Stratego/XT: Program Transformation, SDF: Syntax Definition, Nix: Software Deployment

Pagina: 1