ich habe ein Problem und finde bisher keine schöne Lösung. Vielleicht kann mir jemand weiterhelfen.
Ich habe eine eigene Listenklasse. In dieser Liste können verschiedene Typen von Objekten enthalten sein (Apfel, Birne, Banane usw.). Die Listenklasse ansich stellt einen Iterator zur verfügung um durch alle Elemente zu gehen.
Zusätzlich hätte ich gerne noch einen Typabhängigen Iterator, der je nach Anwendungszweck verwendet werden kann, so in etwa sollte das ausschauen:
Code:
public class Test
{
public static void main(String[] args)
{
MyList myList = new MyList();
myList.add(new Apfel());
myList.add(new Birne());
myList.add(new Banane());
myList.add(new Apfel());
myList.add(new Apfel());
// normaler Iterator über alle Elemente in der Liste
for (Frucht f : myList)
{
// funktioniert
}
// sowas in der Art soll möglich sein
// es sollen direkt "Apfel"-Objekte geliefert werden ohne cast
// die Typen müssen erweiterbar sein
// wenn möglich ohne redundanten code, d.h. nicht für jeden Typ eine eigene Iteratorklasse
for (Apfel a : new Iterator<Apfel>(myList))
{
// ???
}
}
}
Ganz so wird's nicht gehen, da die Typ-Bindungen der Generics zur Laufzweit nicht verfügbar sind. Was gehen würde, wäre so ein Konstruk:
Code:
for (Iterator<Apfel> it = new FilterIterator<Apfel>(Apfel.class, myList); it.hasNext(); ) {
final Apfel a = it.next();
// ???
}
Passt das?
[ edit ] Nicht dass das falsch verstanden wird: Diesen FilterIterator muss man erst bauen. Den gibts nicht (bzw. kenne ich nicht) fertig. Aber so wie oben könnte er funktionieren.
[ edit 2 ] Code-Beispiel nochmal angepasst. Ein Iterator passt nicht in die for-each-Schleife.
Kannst dir eeinen eigenen Iterator schreiben, fertiges gibt es nix.
Müsstest beim Iterieren prüfen, ob es sich um eine Instanz der "richtigen" Klasse handelt, ansonsten weiteriterieren.
Halte ich persönlich für unschönes Design, wegen der Verwendung des instanceOf operators bzw. des Vergleichs der Klassen selbst.
Sehe ich eigentlich nicht so. Man hat oft den Fall, dass man eine Liste allen Obstes hat und dann an einigen Stellen nur die Äpfel iterieren muss. Ist doch mit nem FilterIterator bestmöglich gelöst...
was mir misfällt ist die Abfrage der Typinfo, würde da leiber über eine Interface Methode gehen (boolean isApple()) anstatt die Klasse selbst bzw. instanceOf.
was mir misfällt ist die Abfrage der Typinfo, würde da leiber über eine Interface Methode gehen (boolean isApple()) anstatt die Klasse selbst bzw. instanceOf.
Hier mal die Klasse schnell implementiet. Tut soweit, geht auch in der verkürzten for-each-Schleife so.
Code:
public class FilterIterator<T> implements Iterable<T>, Iterator<T>
{
private Class<?> filterClass;
private MyList list;
private int position;
public FilterIterator(Class<?> filterClass, MyList list)
{
super();
this.filterClass = filterClass;
this.list= list;
position = 0;
}
@Override
public Iterator<T> iterator()
{
return this;
}
@Override
public boolean hasNext()
{
for (int i = position; i < list.getElementCount(); i++)
{
if (list.getElementAt(i).getClass().getName().equals(filterClass.getName()))
{
return true;
}
}
return false;
}
@SuppressWarnings("unchecked")
@Override
public T next()
{
for (int i = position; i < list.getElementCount(); i++)
{
if (list.getElementAt(i).getClass().getName().equals(filterClass.getName()))
{
position = i + 1;
return (T)list.getElementAt(i);
}
}
position = list.getElementCount();
return null;
}
@Override
public void remove()
{
// not implemented
}
}
Code:
for (Apfel a : new FilterIterator<Apfel>(Apfel.class, list))
{
a.print();
}
was mir misfällt ist die Abfrage der Typinfo, würde da leiber über eine Interface Methode gehen (boolean isApple()) anstatt die Klasse selbst bzw. instanceOf.
1. Entweder es ist ein Iterator oder ein Iterable. Ich würde in der Klasse nur den Iterator beschreiben...
2. Die Typ-Prüfung würde ich genauer machen. Und nimm doch für die liste das List-Interface (oder siehe 3.):
Code:
public class FilterIterator<T> implements Iterable<T>, Iterator<T>
{
private Class<? extends T> filterClass;
private List<? super T> list;
private int position;
public FilterIterator(Class<? extends T> filterClass, List<? super T> list) {
3. Der Iterator ist nicht sicher gegen Concurrent-Modification. Daher sollte man eventuell lieber den FilterIterator auf einem Iterator aufbauen.
Das ist mein Vorschlag (inkl. remove und co-modification check, aber noch ungetestet!):
Code:
public class FilterIterator<T> implements Iterator<T> {
private final Class<? extends T> clazz;
private final Iterator<? super T> delegate;
private T next = null;
/** Creates a new <code>FilterIterator</code>. */
public FilterIterator(Class<? extends T> clazz, Iterator<? super T> delegate) {
this.clazz = clazz;
this.delegate = delegate;
}
@SuppressWarnings("unchecked")
@Override
public boolean hasNext() {
while (next == null && delegate.hasNext()) {
final Object test = delegate.next();
if (clazz.isInstance(test)) {
next = (T) test;
}
}
return next != null;
}
@Override
public T next() {
final T value = next;
next = null;
return value;
}
@Override
public void remove() {
delegate.remove();
}
}
[ edit ] hab den clazz-Parameter als "? extends T" abgeändert. Weil's besser ist.
zu 1. wenn man "Iterable"´bei dem Iterator nicht implementiert, kann man ihn ja nicht direkt in einer for-each-Schleife verwenden oder? Bzw. was ist falsch oder schlimm daran "Iterable" auch zu implementieren?
zu2. danke, das mit der Typprüfung ist in deiner Version um längen besser gelöst.
zu 3. was genau macht das "super T" bei "Iterator<? super T>"?
wie schützt diese Implementierung vor Concurrent-Modification?
zu 1. wenn man "Iterable"´bei dem Iterator nicht implementiert, kann man ihn ja nicht direkt in einer for-each-Schleife verwenden oder? Bzw. was ist falsch oder schlimm daran "Iterable" auch zu implementieren?
Ich mag's nicht. Ist vielleicht Geschmackssache, weiß nicht. Ein Datenobjekt ist ggf. iterierbar, ein Iterator eigentlich nicht. Ich würde mir eher in MyList sowas hinzu-implementieren (E ist der ElementTyp der Liste):
Code:
public <T extends E> Iterable<T> filteredIterable(final Class<T> type) {
return new Iterable<T>() {
@Override
public Iterator<T> iterator() {
return new FilterIterator<T>(type, MyList.this.iterator());
}
};
}
Nicole81 hat gesagt.:
zu 3. was genau macht das "super T" bei "Iterator<? super T>"?
Lässt einen Iterator zu, der mit T zuweisungskompatibel ist. Also einen der selbst mit genau Typ T oder einer super-Klasse (inkl. Object) oder einem super-Interface parametrisiert ist.
Nicole81 hat gesagt.:
wie schützt diese Implementierung vor Concurrent-Modification?
ok klar denkfehler, so rufe ich ja die eigene "iterator"-Methode auf und nicht die der Listenklasse.... ok passt aber dass man das so aufruft.. ok wieder was gelernt, danke vielmals!!!
Dieses new Iterable(){...}-Konstrukt ist eine nicht-statische, innere Klasse*. Das bedeutet, sie existiert im Kontext einer einschließenden Klasse (MyList). Wenn Du aus der inneren Klasse zum Beispiel get(7) aufrufen würdest, dann würde der Compiler
zuerst in der Iterable(){...}-Klasse nachsehen,
dort keine get()-Methode finden,
dann in der äußeren Klasse nachsehen,
dort die get()-Methode finden und diese dann verknüpfen.
Da wir aber nicht die get()-Methode aufrufen wollen, sondern die iterator()-Methode, findet der Compiler die nächste Methode, nämlich die aus der Iterable(){...}-Klasse und verknüpft diese. Mit dem Konstrukt "ÄussereKlasse.this" referenziert man die zugehörige this-Referenz der äußeren Klasse, damit der Compiler die Methode von da verknüpft und nicht aus der inneren Klasse.
Ist das Gestammel halbwegs verständlich?
* Außerdem eine anonyme Klasse, da sie keinen echten Namen hat; aber das spielt in dem Zusammenhang keine Rolle.