Frage zu foreachremainig beim Iterator

melmager

Erfahrenes Mitglied
Ich verstehe noch nicht ganz das Konzept von von forEachRemaining :(

Code:
// ich hole aus eine ArrayList mein Iterator
Iterator arraylist = myarraylist.iterator();
while (arraylist.hasNext()) {
  Part part  = (Part)arrayList.next();
// jede menge zeug wird mit part veranstaltet :-)
}

wenn ich sowas auf forEachRemaining umstellen will muss ich doch das Interface Consumer nutzen (so wie ich das verstanden habe).
Was dann zur Folge hat das ich eine Classe schreiben muss die das Implementiert.

Code:
Class CodeFromWhile implements Consumer {
  @Override
  public void accept(Objekt obj) {
    Part part = (Part)obj;
// siehe oben :-)
  }
}

dann müsste doch dann das gehen

Code:
// ich hole aus eine ArrayList mein Iterator
Iterator arraylist = myarraylist.iterator();
CodeFromWhile mystuff = new CodeFromWhile();
// und hier hänge ich grade mit dem Verständniss :-)
arraylist.forEachReamaining(mystuff.accept);

Codebeispiele die ich gesehen haben arbeiten entweder so
Code:
it.forEachRemaining(b -> { state.bld.append(", "); b.accept(me, state); });

oder so

Code:
iterator.forEachRemaining(questions::add);

beide Versionen verstehe ich nicht :(
 

melmager

Erfahrenes Mitglied
Java 8 führt ein Functionales Interface ein
Beschreibung: "functional interface – an interface with a single abstract method"
und das Ganze scheint eine Lambda Expression zu sein.
Scheinbar ist das "b -> { state.bld.append(", "); b.accept(me, state); }" eine anonyme Classe die mit einem functionalen Interface arbeitet.
und scheinbar ist der Teil mit "b.accept();" der Teil mit dem Functionalen Interface Consumer.

Nehme ich erstmal als gegeben hin :cool:
noch 80% fehlen bis AHA Effect :rolleyes:
 

new-bee

Grünschnabel
Jep, mit Java8 sind Lambda-Ausdrücke und Streams neu hinzugekommen.
Da lohnt es sich erst einmal schlau zu machen.

Doch ein Wort zu den "functional interfaces":
Diese wurden nicht direkt mit Java 8 "eingeführt", sie wurden nur besser strukturiert und genutzt. Dabei gilt folgendes.

Funktionale Interfaces definieren nur eine einzige Methode. Dadurch weiß der Compiler, welche Methode aufrufen wird (und wieviele Parameter die Methode besitzt), selbst wenn man den Methodennamen weglässt. Und genau das nutzt man für die Lambda Ausdrücke.

Wenn du also so etwas siehst:

Code:
it.forEachRemaining(b -> { doSomething(); });

Dann ist dies das gleiche wie:

Code:
it.forEachRemaining(new Consumer() {
    public void accept(Object b)
    {
        doSomething();
    }
});

Nur ist dies eben kürzer.

D.h. du bist nicht gezwungen, einen eigenen Consumer zu schreiben, genauso wenig wie man in Java 7 gezwungen wurde, einen eigenen Comparator zu schreiben (geht ja auch anonym).
 

new-bee

Grünschnabel
Doch nochmal etwas genauer:

Mit Java 8 gibt es jetzt quasi "Methoden-Bausteine". Diese Methoden-Bausteine haben keinen Methodennamen und lassen sich daher überall dort verwenden, wo (funktionale) Interfaces definiert wurden, die der selben Konvention entsprechen. Zum Beispiel:

Java:
() -> { System.out.println("Feedback"); }

Kann überall da verwendet werden, wo eine funktionale Methode ohne Parameter gebraucht wird... z.B. für ein Runnable Interface.

Java:
Runnable myRunnable = () -> { System.out.println("Feedback"); };
Thread myThread = new Thread(myRunnable);

// oder gleich
Thread myThread = new Thread( () -> { System.out.println("Feedback"); });

Und da die meisten Anweisungen einfach aufgebaut sind, hat Java 8 gleich mal ein paar Standard Interfaces definiert:

Consumer:
Eine Methode mit einem einzigen beliebigen Parameter aber ohne Rückgabewert.
(weshalb man sagen kann, die Methode "konsumiert den Parameter")

Supplier:
Eine Methode ohne Parameter aber mit einem beliebigen Rückgabewert.
(weshalb man sagen kann, die Methode stellt ein Objekt bereit (supplies))

Predicate:
Eine Methode mit einem einzigen beliebigen Parameter und einen boolean Rückgabewert.
(weshalb man sagen kann, die Methode überprüft den Parameter auf Korrektkeit, Anwendbarkeit. etc.)

Function:

Eine Methode mit einem einzigen beliebigen Parameter und einem beliebigen Rückgabewert.
(also alle simplen Umwandlungs- bzw. Abbildungsmethoden)

BiFunction:
Eine Methode mit zwei beliebigen Parameter und einem beliebigen Rückgabewert.
(quasi der "Rest". Mehr als 2 Parameter werden in komplexere Parameterobjekte zusammengefasst)
 

melmager

Erfahrenes Mitglied
Das hat mich scho ne Ecke Weitergebracht @new-bee

Im Moment sehe ich das Ganze Konzept als eine radikale Verkürzung an.

Code:
// alt bekannt - wir bauen uns ein Arraylist
List<String> strlist = new ArrayList<>();
strlist.add("start");
strlist.add("mitten drin");
strlist.add("das ende ist da");

// ich baue einen String Iterator
Iterator<String> it = strlist.iterator();

//ach so geht das teil 1
it.forEachRemaining(System.out::println);
//also Syntax (ClassWeUse::Methode)

//zweite Version
Iterator<String>  con = strlist.iterator();

con.forEachRemaining(x -> { System.out.println( x.length() ); } );

Der Teil eins ist einfach einer erweiterung der foreach Technik (so sehe ist das)
erst kommt die Classe die ich nutzen will , dann "::" (zwei Doppelpunkte) und dann die Function die mit dem "next()" Wert aufgerufen wird
Das der Nextwert ein String ist lege ich durch "<String>" fest. und da ich nur ein Wert bekomme lassen wir mal locker bei println die runden Klammern weg.

Die zweiter Version ist da schon etwas komplexer
con ist mal wider ein Iterator der ein String ist.
Dadurch wird das x , das ein anonyme Classe ist, zu einer String Classe
Darum kann ich auch "x.length()" machen ,da das eine Function ist die bei String dabei ist.
Innerhalb der geschweiften Klammer kann ich mit x (dem String) also alles treiben was nötig ist

dann habe ich mal die Extendet Version getestet (geht auch :) )

Code:
public  class DoIt {
void doMyStuff(String in) {
System.out.println("doit " + in + " # " + in.length());
}
}

Code:
// extendet variante von zweiter version
DoIt ex = new DoIt();
con.forEachRemaining(x -> { ex.doMyStuff(x); } );

ich denke ich bin bei 90% verstanden :)
 
Zuletzt bearbeitet:

new-bee

Grünschnabel
Im Moment sehe ich das Ganze Konzept als eine radikale Verkürzung an.
Das stimmt auch.

Im Prinzip geht es nur darum, dass Java nicht vollständig objektorientiert ist, und es somit keine Methodenobjekte gibt, die man als Parameter angeben kann... also so etwas wie:

Java:
Method myMethod = ...
iterator.forEachRemaining(myMethod);

Man hat sich mit Java einfach einen Workarounds gebastelt

Lambdas:
Damit kann man annonyme Methoden definieren, indem man nur die Parameter und den Methoden-Rumpf angibt.

Java:
// Ausführliche Lambda-Definition
(String x, String y) -> { return x+y; }

// verkürzte Definition
x,y -> { x+y }
Dies ist dann aber keine echtes Methodenobjekt, sondern ein annonymes Interfaces mit einer einzigen annonymen Methode. Daher kann man sie überall dort verwenden, wo ein beliebiges funktionales Interface erwartet wird (Consumer, Supplier, etc.).

Methodenreferenz:
Da die Lambdas in den häufigsten Fällen aber eh nur benutzt werden würden, um auf eine andere Stelle zu verweisen (wie es ja bei den meisten Swing-Listener der Fall ist):

Java:
// Ausführliche Lambda-Definition
(String x, String y) -> { MyHelper.execute(x,y); }
wäre es doch geschickt, wenn man gleich ein Sprachmittel hätte, um auf eine beliebige existierende Methode zu verweisen.

!!! Bisher fehlte in Java jedoch eine Art und Weise, wie man Methoden eindeutig benennen kann (also die Definition eines Name und keine Anweisung). !!!

Dadurch wurde neben den funktionalen Interfaces und den Lambdas auch noch Methoden-Referenzen hinzugefügt, die stark an C++ erinnern (siehe C++-Programmierung: Namensräume ).

Und was braucht man, um eine Methode eindeutig zu benennen?
=> Vollständiger Klassenname plus Methodenname, getrennt durch einen beliebigen Separator (gewählt wurde "::").

Code:
// Statische Methode
String::valueOf

// Instanzmethode
myObject::getName

// Konstruktor
StringBuilder::new
Und voilà. So kann nun die Logik auch leicht auslagern:

Java:
iterator.forEachRemaining(MyHelper::execute);
Das neue Stilmittel verhindert aber lediglich, dass du Tippfehler machst. Es wird nicht geprüft, ob du sinnvolle Angaben machst. Könntest dort ja auch "Date::new" schreiben.
_______

Warum ich so sehr auf Java Reflections verweise:
Eigentlich gibt es in Java bereits ein Methodenobjekt => java.reflect.Method.
Dieses Objekt definiert auch eine Art "Default"-Methode:

Java:
public Object invoke(Object obj, Object... args) throws IllegalAccessException, IllegalArgumentException,
InvocationTargetException
 
Zuletzt bearbeitet: