Einfaches Beispiel für eine Iterable und einen Iterator in Java

162585
Sahand

Ich war dieses Jahr TA für einen ersten Java-Programmierkurs. Als sehr einfaches Beispiel für Iterables / Iteratoren habe ich den folgenden Code für die Schüler geschrieben. Ich bin gespannt, ob Styling oder andere Verbesserungen möglich sind.

import java.util.NoSuchElementException;
import java.util.Iterator;

public class Range implements Iterable<Integer> {
    private int start, end;

    public Range(int start, int end) {
        this.start = start;
        this.end = end;
    }

    public Iterator<Integer> iterator() {
        return new RangeIterator();
    }

    // Inner class example
    private class RangeIterator implements
                    Iterator<Integer> {
        private int cursor;

        public RangeIterator() {
            this.cursor = Range.this.start;
        }

        public boolean hasNext() {
            return this.cursor < Range.this.end;
        }

        public Integer next() {
            if(this.hasNext()) {
                int current = cursor;
                cursor ++;
                return current;
            }
            throw new NoSuchElementException();
        }

        public void remove() {
            throw new UnsupportedOperationException();
        }
    }


    public static void main(String[] args) {
        Range range = new Range(1, 10);

        // Long way
        Iterator<Integer> it = range.iterator();
        while(it.hasNext()) {
            int cur = it.next();
            System.out.println(cur);
        }

        // Shorter, nicer way:
        // Read ":" as "in"
        for(Integer cur : range) {
            System.out.println(cur);
        }
    }
}
Antworten
42
wie "über @ überschreiben" Anmerkungen? rpg711 vor 6 Jahren 6
Dieser Artikel kann hilfreich sein: http://www.yegor256.com/2015/04/30/iterating-adapter.html yegor256 vor 5 Jahren 0

4 Antworten auf die Frage

35
rolfl

Variablen

Ich verstehe, warum Sie das haben Range.this.endund Range.this.startum Verwirrung darüber zu vermeiden, woher diese Variablen kommen ... Wenn Sie das Range.thisals Teil der Unterrichtsübung benötigen, dann sicher. Ansonsten würde ich drei Dinge empfehlen ....

  1. rangeals Präfix hinzufügen, obwohl es leicht redundant ist
  2. Machen Sie sie endgültig ...
  3. eine Variable pro Zeile ... (macht Revisionskontrolldifferenzen / Patches leichter lesbar)

Der Code würde folgendermaßen aussehen:

private final int rangeStart;
private final int rangeEnd;

Dann Range.this.startwäre alles nur noch so rangeStartetc.

Verschachtelte Klassen

Ihre Iteratorklasse ist eine nicht statische Klasse. Sie kann also auf den Anfang und das Ende der Range der äußeren Klasse verweisen.

In diesem Fall kann die verschachtelte Klasse sehr einfach in eine statische Klasse geändert werden. Dies hat das Potenzial, die Speicherverwaltung zu vereinfachen, da der Iterator keinen Verweis auf den umschließenden Bereich benötigt.

Betrachten Sie eine privat-statische Iterator-Instanz:

// Inner class example
private static final class RangeIterator implements
                Iterator<Integer> {
    private int cursor;
    private final int end;

    public RangeIterator(int start, int end) {
        this.cursor = start;
        this.end = end;
    }

    public boolean hasNext() {
        return this.cursor < end;
    }

    public Integer next() {
        if(this.hasNext()) {
            int current = cursor;
            cursor ++;
            return current;
        }
        throw new NoSuchElementException();
    }

    public void remove() {
        throw new UnsupportedOperationException();
    }
}

Diese statische Klasse macht die Rückverweise auf Range.this... komplett überflüssig.

Der neue Iterator wird einfach aufgerufen mit:

public Iterator<Integer> iterator() {
    return new RangeIterator(start, end);
}

Vorabvalidierung

Es ist besser, den Status vorab zu überprüfen, als zu einem Fehler zu führen ... Dieser Code:

    public Integer next() {
        if(this.hasNext()) {
            int current = cursor;
            cursor ++;
            return current;
        }
        throw new NoSuchElementException();
    }

wäre besser als:

    public Integer next() {
        if(!this.hasNext()) {
            throw new NoSuchElementException();
        }
        int current = cursor;
        cursor ++;
        return current;
    }

Post-Increment

Dieser Block kann vereinfacht werden:

        int current = cursor;
        cursor ++;
        return current;

um nur:

return cursor++;

obwohl ich mir vorstellen kann, dass dies als erzieherischer Trick geschieht.

Ganzzahl als Beispiel

Ich mache mir Sorgen, dass Integer aufgrund des Int-Auto-Bocxing nicht die richtige Wahl für den Datentyp ist. Möglicherweise möchten Sie ein Nicht-Primitiv als Daten betrachten.

Autoboxing ist etwas, was verwirren wird.

Fazit

Ansonsten sehe ich Probleme nicht viel.

Vielen Dank. Ich habe nicht an `` final`` gedacht, es ist eine großartige Idee. Ich stimme auch zu, dass eine private statische Klasse besser ist als eine innere Klasse, aber ich habe vergessen zu erwähnen, dass der Code ihnen auch ein Beispiel für eine innere Klasse geben sollte, um sie weiter zu zeigen. Das Post-Inkrement wurde auf die längere Art und Weise durchgeführt, um Verwirrung zu vermeiden, da die Schüler es seltsam verwirrend finden. Interessantes über die Verwendung eines nicht primitiven Typs. Ich wollte der Einfachheit halber ein Beispiel für eine Iteration angeben, bei der es sich nicht um einen Container handelt (z. B. verknüpfte Liste, Hash-Set usw.). Können Sie sich eines vorstellen, das kein Primitiv verwenden würde? Sahand vor 6 Jahren 0
Gute Punkte zum "Pre-Validate", um einige Formulierungen einzufügen: Dieses Idiom wird als "Guard-Klausel" (http://c2.com/cgi/wiki?GuardClause) bezeichnet und prüft die Vorbedingungen mdo vor 6 Jahren 4
12
200_success

Insgesamt ist es ziemlich guter Code, um daraus zu lernen.

Funktionalität

Ich finde es gut, dass Sie die inklusive-exklusive Konvention für die Unter- bzw. die Obergrenze verwendet haben. Die Begründung für dieses Design wäre ein interessantes Diskussionsthema.

Ich empfehle, zur Vereinfachung einen zweiten Konstruktor hinzuzufügen:

public Range(int end) {
    this(0, end);
}

Es sollte wahrscheinlich Getter für start()und geben end(). Technisch sollten Sie außer Kraft setzen .equals()und .hashCode()als gut, aber vielleicht ist es in Ordnung, sie der Einfachheit halber auszulassen.

Stil

Wie @ rpg711 feststellte, @Overridewäre es eine gute Praxis, die Anmerkung überall zu platzieren. Es würde auch den Schülern helfen zu erkennen, welche Methoden obligatorisch sind (praktisch alle).

JavaDoc wäre eine gute Angewohnheit, um zu unterrichten. Zumindest dokumentieren Sie die äußere Klasse und die innere Klasse und wahrscheinlich auch deren Konstrukteure.

Es wäre konventionellen einen Raum nach dem setzen if, forund whileKeywords. Sie sehen weniger aus wie Funktionsaufrufe auf diese Weise.

Erklären startund endwie finalhelfen könnte, den Studenten zu betonen, dass das Rangeunveränderlich ist und nur der RangeIteratorÄnderungsstand. Vielleicht finalwürde das Hinzufügen einige Bedenken von @ rolfl über die innere Klasse, die sich auf Range.this.startund bezieht, verringern Range.this.end.

In Übereinstimmung mit @rolfl würde ich es auch persönlich bevorzugen

    @Override
    public Integer next() {
        if (!this.hasNext()) {
            throw new NoSuchElementException();
        }
        // The post-increment magically takes effect _after_
        // returning.  This is equivalent to
        //
        //     int value = this.cursor++;
        //     return value;
        //
        return this.cursor++;
    }

… obwohl ich verstehen kann, wenn Sie sich entscheiden, die Schüler nicht mit diesen Kleinigkeiten zu belasten.

Testfall

Es wäre nützlich zu veranschaulichen, dass zwei RangeIteratorsunabhängig voneinander bleiben. Vielleicht könnte dies ein gutes Beispiel sein?

Range digits = new Range(0, 10);
for (Integer tensDigit : digits) {
    for (Integer onesDigit : digits) {
        System.out.format("%s%s ", tensDigit, onesDigit);
    }
    System.out.println();
}
6
janos

@rolfl hat es total genagelt. Nur noch ein paar Nitpicks übrig:

  • Ich würde alle sinnlosen Kommentare weglassen, es sei denn, sie dienen einem Zweck, wenn Sie dies lehren
  • Fügen Sie zur @Overridebesseren Übersicht die Anmerkungen hinzu, wenn Sie nicht in einer IDE lesen
  • Jedes Mal, wenn Sie fallen kann this.aus this.cursor, würde ich es fallen
  • Die Art und Weise, wie Sie den Abstand um Klammern verwenden, entspricht nicht dem Standard. Verwenden Sie die Neuformatierungsfunktion Ihrer IDE (entspricht Control-Shift-fEclipse).

Ich denke, es ist eine großartige Idee, hier zu fragen, bevor Sie sich in Ihrer Klasse präsentieren!

Kommentar hier, weil Sie erwähnen ... die Kommentare: Ich persönlich bevorzuge mehrzeilige Kommentare als // - und in meiner Codebase bestehe ich auf mindestens einem Javadoc auf Klassenebene (was ist dieses Range-Konzept überhaupt?) mdo vor 6 Jahren 0
und @Override dient nicht nur der Lesbarkeit, weil es korrekte Überschreibungen mit Kompilierungsfehlern sichert mdo vor 6 Jahren 2
3
David Herrera

Ich denke, einige Schüler würden ein Beispiel ohne innere Klassen schätzen:

Range kann den Iterator ohne innere Klasse implementieren. Sie müssen lediglich den Cursor auf den Startwert zurücksetzen. Hier setze ich den Cursor in der Iterator-Methode und in der nächsten Methode zurück, sobald der Iterator durch den Bereich gelaufen ist. Es funktioniert für die vorgeschlagenen Beispiele. Natürlich behält der Iterator die Zustände nicht unabhängig und funktioniert auch nicht für komplexere Beispiele, aber ich muss keine Konstruktorargumente an eine innere Klasse übergeben.

import java.util.NoSuchElementException;
import java.util.Iterator;

public class Range implements Iterable<Integer>, Iterator<Integer> {
    private int start, end, cursor;

    public Range(int start, int end) {
        this.start = start;
        this.end = end;
    }

    public Iterator<Integer> iterator() {
        cursor = start;
        return this;
    }

    public boolean hasNext() {
        return cursor < end;
    }

    public Integer next() {
        if(!hasNext()) {
            cursor = start;
            throw new NoSuchElementException();
        }
        return cursor++;
    }

    public void remove() {
        throw new UnsupportedOperationException();
    }

    public static void main(String[] args) {
        Range range = new Range(1, 10);

        // Long way
        Iterator<Integer> it = range.iterator();
        while(it.hasNext()) {
            int cur = it.next();
            System.out.println(cur);
        }

        // Shorter, nicer way:
        // Read ":" as "in"
        for(Integer cur : range) {
            System.out.println(cur);
        }

        Range digits = new Range(0, 10);
        for (Integer tensDigit : digits) {
            for (Integer onesDigit : digits) {
                System.out.format("%s%s ", tensDigit, onesDigit);
        }
        System.out.println();
        }
    }
}