2017-08-25 3 views
2

ich die Java 8 Stream-API wie folgt aus:Overhead: Konvertieren zwischen Primitive Streams vs. Boxen

private Function<Long, Float> process;   // Intermediate step (& types) 

private long getWeekFrequency(final ScheduleWeek week) { 
    return week.doStreamStuff().count();  // Stream<>.count() returns long 
} 

@Override 
public float analyse(final Schedule sample) { 
    return (float) sample      // cast back to float 
      .getWeeks() 
      .stream() 
      .mapToLong(this::getWeekFrequency) // object to long 
      .mapToDouble(process::apply)  // widen float to double 
      .sum(); 
} 

@Override 
public String explain(final Schedule sample) { 
    return sample 
      .getWeeks() 
      .stream() 
      .map(this::getWeekFrequency)  // change stream type? 
      .map(String::valueOf) 
      .collect(Collectors.joining(", ")); 
} 

Fragen

  • Ich nehme an, es Overhead ist, wenn zwischen dem Objekt zu ändern/primitive Stream-Typen ... Wie verhält es sich mit dem Boxen-Overhead, wenn ich mich an Stream <> halte?

  • Was ist, wenn ich später zurückwechsle?

Konkret:
In Analytiker soll ich .map(...).mapToDouble(...) verwenden?
In erklären, sollte ich .mapToLong(...).mapToObj(...) verwenden?

+3

ändern Ich habe eine andere Frage hinzuzufügen: spielt es so viel in Ihrem System aus? Erleben/erwarten Sie irgendwelche Performance-Probleme? "Soll ich nur Double/Double überall verwenden" - was sagen Ihre Geschäftsanforderungen? Wie sieht dein Modell aus? – Thomas

+1

Wenn dieser Code funktioniert, den Sie verbessern möchten, würde die Frage wahrscheinlich weitere interessante Antworten auf die [CodeReview SE] (https://codereview.stackexchange.com/) ziehen. Es kann jedoch eine Überarbeitung erforderlich sein, um die Teile, die Sie vereinfacht haben, sowie die Definition der beteiligten Klassen einzuschließen. – Aaron

+0

@Thomas - Ich bin ziemlich zufrieden mit der Leistung (es ist ausreichend, also schätze ich die Lesbarkeit). Ich denke, ich möchte mehr darüber wissen, ob ich 'mapToLong()' vs 'map()' richtig im Zwischenteil eines Streams verwende. Ich werde das Double/Double Bit umschreiben ... das ist zu offen. – AjahnCharles

Antwort

3

Also lassen Sie uns diese brechen:

.mapToLong(this::getWeekFrequency) 

gibt Ihnen eine primitive lang.

.mapToDouble(process::apply) 

Diese primitive lange auf eine Long eingerahmt wird, weil die process Funktion es erfordert. process gibt eine Float zurück, die einem primitiven Double zugeordnet ist (über Float.doubleValue()).

Diese werden summiert und die Summe wird in ein primitives Float umgewandelt (schmaler, aber sicherer), das dann zurückgegeben wird.


Also, wie können wir einige der Autoboxing loswerden? Wir wollen einen FunctionalInterface, der genau zu unserer process Funktion passt, ohne irgendwelche Boxklassen zu verwenden. Es gibt nicht ein wir off-the-shelf verwenden können, aber wir können es leicht definieren, wie so:

private LongToFloatFunction process; 

und halten alles andere gleich:

@FunctionalInterface 
public interface LongToFloatFunction 
{ 
    float apply(long l); 
} 

wir unsere Erklärung Dann ändern was jedes automatische Boxen verhindert. Das von der Funktion zurückgegebene primitive Float wird automatisch auf ein primitives Double erweitert.

+0

Danke Michael; Dies hilft wirklich, die Punkte meines Verständnisses zu verbinden (insbesondere, die primitiven 'Funktion'-Typen besser zu kennen und die Tatsache, dass ich meine eigenen primitiven-> primitiven' Funktionsinterfaces erstellen konnte, wo keine Framework-Implementierung existiert) – AjahnCharles

+0

In meinem vollen Kontext , meine Prozessfunktion hat eine generische Logik unabhängig vom Eingabetyp (dh ich kann Float in float ändern, aber ich möchte wahrscheinlich den Eingabeparameter "float apply (T t)" behalten. In Ihrer vorherigen Editierung haben Sie den OOTB 'erwähnt ToDoubleFunction ", die für mich passender sein könnte (obwohl ich vielleicht meine eigene 'ToFloatFunction' machen werde). Wenn die Eingabe immer mit der Eingabe für die Funktion eingerahmt wird, bedeutet das' .map (myToFloatFunction) .mapToDouble (Prozess: : apply) 'ist effizienter? (da der long-Wert trotzdem eingerahmt ist und das Ändern des Stream-Typs vermieden wird) – AjahnCharles

+1

Für die meisten praktischen Zwecke gibt es keinen Grund für diese' LongToFloatFunction' als die bereits existierende 'LongToDoubleFunction' i s ausreichend. Für Zwischenwerte gibt es kaum einen Grund, sie auf den Wertsatz "float" herunterzufahren, wobei "float" nur für * storage * nützlich ist. Dies wird am besten durch die Tatsache veranschaulicht, dass es überhaupt keinen "FloatStream" gibt, da eine * processing * -Pipeline keinen Vorteil davon hat, "float" anstelle von "double" zu verwenden. Der nette Nebeneffekt ist, dass Sie die Funktion direkt an 'LongStream.mapToDouble' übergeben können, ohne sie dann über' :: apply' anzupassen. – Holger

2

Nun, es von Ihrer Definition scheint, dass process Art wie folgt aussieht:

double process (long value) { 
     // do something 
} 

Als solche, wenn Sie tun: map(...).mapToDouble Sie würde ein Objekt vom Typ erschaffen Long jedes Mal, es nur unbox unmittelbar nach in process verwendet werden. Ich würde den Code so lassen, wie er ist, um die primitive Implementierung zu verwenden, die dies vermeiden würde.

Der zweite verwendet String#valueOf. Im Fall von long wird String.valueOf(l) aufgerufen, die auf dem Primitiv funktioniert: Long.toString(l).

Bei Object wird die gleiche Methode aufgerufen, aber mit dem Vorbehalt, dass das erste Boxen passiert. Also würde ich es zu mapToLong

+0

+1: Vielen Dank für Ihre Antwort @Eugene; Es hilft mir, die internen Schritte detaillierter zu sehen. Ich ging mit Michaels Antwort, weil das zusätzliche Verständnis von primitiven Funktions- und Funktionsinterfaces für meine Lösung wichtig ist. – AjahnCharles

Verwandte Themen