Toon posts:

[Java 1.5] Generics - unchecked conversion

Pagina: 1
Acties:

Verwijderd

Topicstarter
Hoi,

ben voor het eerst bezig met java en generics. Zie onderstaand stukje code. Het gaat om een interface voor een Tree class, en een AbstractTree die alvast wat triviale functionaliteit implementeert.

Ik krijg een "unchecked conversion" bij het compileren, verder lijkt het compileren wel goed te gaan, maar mijn vraag is of ik hier iets aan kan/moet doen of dat de code gewoon correct is.

(Overigens heb ik ervoor gekozen om zowel een interface als een abstracte class te gebruiken, zodat ik ook Tree implementaties kan maken die heel anders werken dan AbstractTree. Ondertussen hoef ik dan voor classes die wel van AbstractTree erven, geen code te dupliceren....)

Java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
interface Tree<E> {  
    public E get();
    public boolean isLeaf();
    public Tree getParent();
    public ArrayList<Tree<E>> getChildren();
    public Iterator<Tree<E>> iterator();
}

abstract class AbstractTree<E> implements Tree{
    protected E element;
    protected AbstractTree<E> parent;

    public AbstractTree( E el ){ element = el; parent = null; }
    protected AbstractTree( E el, AbstractTree<E> p ){ element = el; parent = p; }

    public E get(){ return element; }
    public Tree getParent(){ return parent; }

    // Warning hier: "try.java": [unchecked] unchecked conversion; 
    // found: java.util.Iterator, 
    // required: java.util.Iterator<tryout.AbstractTree<E>>
    public Iterator<AbstractTree<E>> iterator(){ return getChildren().iterator(); };
}

  • Alarmnummer
  • Registratie: Juli 2001
  • Laatst online: 09-07-2024

Alarmnummer

-= Tja =-

2 kleine dingen (geen antwoord op je vraag)

1) Waarom heeft de getParent geen geparametriseerd return type? Dus Tree<E>
2) waarom gebruik je een abstracttree<E> als parent. Waarom niet ruimere parents toelaten, dus Tree<E>

En nu de antwoord op je vraag. Je bent vergeten de typeparameter van AbstractTree door te geven aan Tree.

dus:
abstract class AbstractTree<E> implements Tree<E>{....}

en maak dit van je iterator:
public Iterator<Tree<E>> iterator(){ return getChildren().iterator(); }

Die andere wil je denk ik niet :)

[ Voor 57% gewijzigd door Alarmnummer op 26-11-2005 15:46 ]


Verwijderd

Topicstarter
Wow. Heel snel, volledig, bruikbaar antwoord. Dank u!

nog een reactie:

1) klopt inderdaad, heb ik inmiddels gewijzigd
2) hmm.. tot nu toe had ik het idee om geen verschillende Tree types te mixen als parent en child. Maar op zich kan dat natuurlijk wel...

  • Confusion
  • Registratie: April 2001
  • Laatst online: 01-03-2024

Confusion

Fallen from grace

Edit: Nevermind; als je, zoals Alarmnummer zegt, de typeparameter goed meegeeft, dan blijkt die conversie helemaal niet mogen.


Ik denk dat het volgende het probleem is: [geknipt: het enige probleem zit in mijn hoofd ;)]

[ Voor 75% gewijzigd door Confusion op 26-11-2005 16:06 ]

Wie trösten wir uns, die Mörder aller Mörder?


  • Alarmnummer
  • Registratie: Juli 2001
  • Laatst online: 09-07-2024

Alarmnummer

-= Tja =-

Nog een opmerking: Ipv arraylist te returnen, return gewoon List. Het gaat niemand wat aan welke List implementatie jij gebruikt en als jij in de toekomst een andere implementatie wilt gebruiken, kom je in de problemen. Dus geef zo weinig mogelijk implementatie specifieke details bloot (vooral in interfaces!)

Verwijderd

Topicstarter
Nogmaals bedankt Alarmnummer, je tips zijn waardevol. Heb returntype van getChildren() inmiddels aangepast naar List<Tree<E>>.

En dan nu een nieuwe vraag. Stel ik wil een class maken die speciale functionaliteit heeft voor Tree's waar File's in hangen. Deze class moet de abstracte functie getChildren() overriden. Maar het is mij nog niet helemaal duidelijk welke returntypes wel en welke niet legaal zijn. Ik mag wel een ArrayList gebruiken, want dit is een subclass van List, maar die moet wel dezelfde parameters hebben..?

Java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
interface Tree<E> {
    public E get();
    public boolean isLeaf();
    public Tree<E> getParent();
    public List<Tree<E>> getChildren();
    public Iterator<Tree<E>> iterator();
}

abstract class AbstractTree<E> implements Tree<E>{
    protected E element;
    protected Tree<E> parent;

    public AbstractTree( E el ){ element = el; parent = null; }
    protected AbstractTree( E el, AbstractTree<E> p ){ element = el; parent = p; }

    public E get(){ return element; }
    public Tree<E> getParent(){ return parent; }
    public Iterator<Tree<E>> iterator(){ return getChildren().iterator(); };
}

class FileTree extends AbstractTree<File>{
    // waarom mag ik hier niet zeggen
    // public ArrayList<FileTree> getChildren()  ???
    // FileTree is toch een subclass van Tree<File> ?
    // overigens mag 
    // public ArrayList<AbstractTree<File>> getChildren() ook niet??
    public ArrayList<Tree<File>> getChildren(){
         // bla bla bla
    }
}

[ Voor 29% gewijzigd door Verwijderd op 26-11-2005 16:21 . Reden: code was incompleet ]


Verwijderd

Je moet je heel hard overtuigen dat List<A> geen subtype is van List<B>, ook al is A een subtype van B (A extends B, A implements B, ...). Eenmaal je dat door hebt, is het heel simpel.

  • Alarmnummer
  • Registratie: Juli 2001
  • Laatst online: 09-07-2024

Alarmnummer

-= Tja =-

Verwijderd schreef op zaterdag 26 november 2005 @ 16:18:
En dan nu een nieuwe vraag. Stel ik wil een class maken die speciale functionaliteit heeft voor Tree's waar File's in hangen. Deze class moet de abstracte functie getChildren() overriden.
Als je gebruik maakt van de java.io.File class zou je het volgende kunnen doen:

class FileTree extends AbstractTree<File>{....}

Op die manier heb je AbstractTree geparametriseerd met de file class.

Je zou ook zelf een file type kunnen maken (bv een interface) en dan verder parametrisatie later toevoegen:

class FileTree<F extends FileType> extends AbstractTree<F>{....}

Dit heet ook wel een bounded parametrized type.
Maar het is mij nog niet helemaal duidelijk welke returntypes wel en welke niet legaal zijn.
Vanaf java 5 is het toegestaan om een returntype terug te sturen dat een subtype is van het orgineel (je mag een scherper returntype terug sturen): covariante return types.

vb
code:
1
2
3
4
5
6
7
interface Foo{
     Werknemer getWerknemer();
}

class Bar extends Foo{
     Slager getWerknemer();
}


Dit is dus toegestaan.

Variantie bij geparametriseerde types is behoorlijk complex en om eerlijk te zijn laat mijn kennis het hier ook afweten. Ik heb ooit eens een typesysteem in elkaar gezet voor geparametriseerde types en ik vond dit toen ook al complex.
class FileTree extends AbstractTree<File>{
// waarom mag ik hier niet zeggen
// public ArrayList<FileTree> getChildren() ???
// FileTree is toch een subclass van Tree<File> ?
// overigens mag
// public ArrayList<AbstractTree<File>> getChildren() ook niet??
public ArrayList<Tree<File>> getChildren(){
// bla bla bla
}
}
[/code]
Dat heeft dus te maken met variantie problematiek. Er zijn wel mensen op het forum (oa mbravenboer) die dit wel uit kan leggen. Dus martin, waar blijf je :)

  • Confusion
  • Registratie: April 2001
  • Laatst online: 01-03-2024

Confusion

Fallen from grace

Verwijderd schreef op zaterdag 26 november 2005 @ 16:18:
code:
1
2
3
4
5
6
7
8
9
10
class FileTree extends AbstractTree<File>{
    // waarom mag ik hier niet zeggen
    // public ArrayList<FileTree> getChildren()  ???
    // FileTree is toch een subclass van Tree<File> ?
    // overigens mag 
    // public ArrayList<AbstractTree<File>> getChildren() ook niet??
    public ArrayList<Tree<File>> getChildren(){
         // bla bla bla
    }
}
In de generics tutorial wordt het als volgt uitgelegd: stel je probeert een List<String> te aliassen als een List<Object>:
code:
1
2
List<String> ls = new ArrayList<String>(); //1
List<Object> lo = ls; //2

Rege 2 klinkt logisch. Maar dan zou ook het volgende mogen:
code:
1
2
lo.add(new Object()); // 3
String s = ls.get(0); // 4: attempts to assign an Object to a String!

Daarmee zou je de typesafety die generics proberen te introduceren weer overboord gooien.

Evenzo mag je geen covariant parametertype gebruiken, want dat zou betekenen dat als je een ArrayList<FileTree> in een tussengelegen stadium als een ArrayList<Tree<File>> beschouwt, je er een element aan toe zou kunnen voegen dat alleen interface Tree<File> implementeert. Als je er dan later een methode van FileTree op aanroept, dan zou de compiler dat prima moeten vinden, maar runtime zit daar automatisch een expliciete cast naar FileTree op en kan je dus een ClassCastException verwachten. Precies wat generics moeten voorkomen.

[ Voor 3% gewijzigd door Confusion op 26-11-2005 20:00 . Reden: Er waren wat [nohtml] tags nodig. ]

Wie trösten wir uns, die Mörder aller Mörder?

Pagina: 1