Java reduced

public class Test {
    private static double round(double d) {
        return ((long) (d < 0 ? d * 100 - 0.5 : d * 100 + 0.5)) / 100.0;
    }

    public static void main(String ... args){
        double mwst = 0.19;
        BigDecimal mwBig = BigDecimal.valueOf(0.19);

        double lo = 0.0;
        double hi = 100.0;

        double[] values =
                new Random(42)
                    .doubles(100)
                    .map(d -> d * ((hi - lo) + lo))
                    .map(Test::round)
                    .toArray();

        System.out.println(
            Arrays.stream(values)
                .mapToObj(BigDecimal::valueOf)
                .map(b -> b.multiply(mwBig))
                .reduce(BigDecimal.ZERO, BigDecimal::add));

        System.out.println(
            Arrays.stream(values)
                .mapToObj(BigDecimal::valueOf)
                .reduce(BigDecimal.ZERO, BigDecimal::add)
                .multiply(mwBig));

        System.out.println(
            Arrays.stream(values)
                .map(d -> d * mwst).reduce(0.0, (a, b) -> a + b));

        System.out.println(
                Arrays.stream(values)
                    .reduce(0.0, (a, b) -> a + b) * mwst
                );
    }
}

codet @ThreadPool in http://forum.byte-welt.net/threads/13147-BigDecimal-equals?p=104307&viewfull=1#post104307

ist das die neue Java-Welt? wird sowas wirklich intensiv auch für Dummy-Kram wie double/ BigDecimal-Berechnungen verwendet?


ein bedenkenswerter Punkt, der mir gerade auffällt, ist unterschiedliche Behandlung von einzelnen Codezeilen zu Arrays:
bei einem double d schreibst man anscheinend immer noch

BigDecimal brutto = BigDecimal.valueOf(d).multiply(mwBig);

dazu hat eine klassische Schleife a la

BigDecimal sum = BigDecimal.ZERO;
for (int i=0;i<d.length; i++) {
    BigDecimal brutto = BigDecimal.valueOf(d**).multiply(mwBig);
    sum = sum.add(brutto );
}

viel drumherum Noise, bei welchem man auch Fehler machen kann, aber die wichtige Berechnung kann man zumindest angenehm ähnlich gecodet wiederfinden,

ist es nicht bedenklich, wenn daraus ziemlich verrückt anderes a la

                .map(b -> b.multiply(mwBig))
```wird?
keine Gefahr von Übersehen von Rechenzeichen (im double-Fall), Variablen, Konstanten, richtigen Methoden, durch die andere Darstellung?
Gefahr von Nichtfinden bei Textsuche nach dieser Art Code, Aufwand für IDEs bei Refactorn usw?

etwas akzeptabler wäre es für mich ja noch, wenn man die Einzelvariante dann ebenso darstellt: 
```double d = ..;
BigDecimal brutto = Pump.up(d).mapToObj(BigDecimal::valueOf).map(b -> b.multiply(mwBig));

nie mehr normale Java-Aufrufe, stattdessen eine Code-Welt von Doppel-Doppelpunkten :wink: , na soweit wird es wohl nicht kommen

ein gemäßigter Einsatz nur mit map auf Listen, Iteratoren in zentralen Programmklassen, in wenigen % des Code,
nur mit Aufruf von individuellen wichtigen Methoden, statt BigDecimal- oder double-Berechnungen, das ginge ja noch,
bessere Syntax für den im Moment kruden seltenen Einsatz von anonymen inneren Klassen usw.,
aber fast überall für Arrays wie man hier als ein Beispiel sehen kann?


die Aussage aus dem Alten Hasen-Thread

[QUOTE=Bleiglanz]So gesehen wird man als alter Hase ja gerade im Java Umfeld noch lange sein auskommen haben:

Java ohne Generics
mit rohen JDBC-Zugriffen
mit gewaltigen "Swing"er-Orgien
mit java.util.Date und anderen Verrückten
J2EE (vor EJB3) mit riesigen XML-Descriptoren
mit vielen, vielen überflüssigen Casts aus der Zeit vor dem Boxing
Wirrnis ohne das java.util.concurrent Paket

usw. usf.
[/QUOTE]
könnte bald eine weitaus dramatischere Bedeutung haben, wenn Java demnächst keine simplen Schleifen mehr kennt…, eine ganz neue Sprache

Solange es Schleifen gibt, werden sie verwendet werden. Die sind ja in C# auch nicht ausgestorben obwohl es schon ewig Linq gibt.

Also es gehen noch mehr Doppelpunkte, die Lambdaausdrücke zur Summenbildung kann man entfernen wenn man die statischen Methoden an den Wrapperklassen nutzt oder die Argumente umdreht.

Arrays.stream(values).map(d -> d * mwst).reduce(0.0, (a,b)-> a + b));
//   wird zu
Arrays.stream(values).map(d -> d * mwst).reduce(0.0, Double::sum));
//    wird zu
Arrays.stream(values).mapToObj(BigDecimal::valueOf).map(mwBig::multiply).reduce(BigDecimal.ZERO, BigDecimal::add));```

Letzteres würde man eigentlich noch weiter zusammenfassen. Dazu könnte man sich eine kleine Utils-Klasse für BigDecimal schreiben.

```public final class BigDecimalUtils{
        private BigDecimalUtils(){};

        public static DoubleFunction<BigDecimal> multiplyWith(BigDecimal b){
            return d -> BigDecimal.valueOf(d).multiply(b);
        }
        
        public static DoubleFunction<BigDecimal> multiplyWith(double value){
            BigDecimal val = BigDecimal.valueOf(value);
            return d -> BigDecimal.valueOf(d).multiply(val);
        }
    }```

Und im Code kann es so verwendet werden.

DoubleFunction withMwSt19 = BigDecimalUtils.multiplyWith(BigDecimal.valueOf(1.19));
//DoubleFunction withMwSt19 = BigDecimalUtils.multiplyWith(1.19);
Arrays.stream(values).mapToObj(withMwSt19::apply).reduce(BigDecimal.ZERO, BigDecimal::add);```

Das ist dann auch etwas leserlicher und man hat dieses BigDecimal.valueOf versteckt. Wie sich das Ganze beim Refactoring verhält habe ich noch nicht getestet. Umbenennungen in Eclipse funktionieren jedenfalls ganz brauchbar. Und keine Bange das sich normale Schleifen in Luft auflösen, dafür ist die Syntax von Java einfach zu sperrig und es gibt Hindernisse wie das Variablen die in den Lambda-Kontext reingezogen wurden final sein müssen.

Ich denke, das hat ein bißchen was von einem “Hype Cycle”: Es gibt jetzt sicher Stellen, wo Lambdas verwendet werden, auch wenn es nicht angebracht ist - nur weil sie halt neu und cool sind (kein Vorwurf an ThreadPool ;)). Ich erinnere mich auch an einige “Sünden”, die ich begangen habe, als Generics neu waren (kaum zu glauben wie tief man die schachteln kann :D)

Es gibt schon Dinge, die man mit Lambdas wahnsinnig elegant und kompakt schreiben kann. Und, wie so oft, resultiert aus dieser Kompaktheit u.U. kryptischSTer Code. Ich denke z.B. auch, dass sie es z.B. sehr schwer machen können, bestimmte Fehler zu finden. Für die Frage, an welchen Stellen die denn nun wirklich “angebracht” sind, muss man wohl auch erst ein Gespür entwickeln. Aber sowas wie die Collectors sind schon ziemlich mächtig…

Mir persönlich ist das neue Java immer noch ein wenig zu Bloated

[clojure](reduce + (map #(* % 0.19) (take 100 (repeatedly #(Math/random)))))[/clojure]

bzw. der Lesbarkeit wegen, dann gibt es am Ende auch keine 5 schließenden Klammern.
[clojure](->> #(Math/random)
repeatedly
(take 100)
(map #(* % 0.19))
(reduce +))[/clojure]

Aber es geht schon man in eine gute Richtung.

  1. Ich würde auch immer Lambdas verwenden (so wie Threadpool): wenn die Lesbarkeit beim ersten Durchlesen und die Fehlersuche nicht leidet, spricht nichts dagegen.

  2. Früher hätte man lange Verkettungen x.foo().bar().baz().troll().gnurf().bloez() eher belächelt - passen einfach nicht gut zu Exceptions, und was genau macht der baz() da in der Mitte?

  3. Vergleiche mit anderen Programmiersprachen sind absurd - so etwas sieht in Haskell immer schöner, klarer und sauberer aus. Spricht aber trotzdem nicht für Haskell.

Ich habe aber auch einen Kollegen, der so etwas NIE machen würde. Seiner Meinung nach ist Lesbarkeit immer das wichtigste - auch ein Anfänger muss den Code innerhalb
von ein paar Minuten verstehen können - keine Tricks, keine Abkürzungen, nichts esoterisches nur weil dann 0.00001ms eingespart werden, etc. Liegt aber daran, dass der oft
in verschiedenen Projekten mithilft, so dass sein Code fast immer anderen zur Pflege in die Hände gelegt wird.

Ich denke das ist ein Zeitproblem: Nichts spricht gegen

for(int i=0;i<max;i++){
    
}

und aber irgendwann sind die Leute, die einfach x.map().sum() usw. verwenden in der Mehrheit.

Checked Exceptions sind auch noch ein “Problem”, mit dem Lambdas nicht umgehen können…

das klingt ja gar nicht so schlecht, falls das zur Umerziehung hin zu RuntimeExceptions mithilft :wink:

jede solche Aktion wie DBQuerys in schlechten Frameworks (JDBC) oder String zu Zahl parsen hat auch so schon Wrapper-Methode verdient

@SlaterB

Was bei RuntimeExceptions verloren geht ist das ausdrückliche Hinweisen über das Typsystem das etwas schiefgegangen sein könnte. Optional ist z.B. ein Weg um die Abwesenheit von Werten zu behandeln. Jedoch reicht das nicht, weil Optional keine Fehler transportiert. Dafür könnte man dann ein Either nehmen (Landei hat sicher eins in neco4j rumliegen) aber noch besser wäre ein Try als Typ den man auswerten kann. Es sind verschiedene Vorgehensweisen, und eine Frage wie und was man dem Verwender mitteilen möchte.

wie man hier sieht bin ich eher ‚konservativ‘,
oder zumindest bei Streitfragen ‚Schleifen mies → wir brauchen Lamba‘ oder auch ‚Checked Exceptions mies → RuntimeException ist hipp‘ entspannt,
mich brauchst du nicht so sehr zu überzeugen,

da Lamda-Fans im Schnitt sicher auch bei den anderen modernen Theorien liegen, also auch RuntimeExcptions bevorzugen (wie ich in diesem Fall auch),
ist der von Natac angebrachte Punkt nur wenig Gegenargument, sondern in gewisser Weise ein Pluspunkt für Lamda,
diese nicht direkt sichtbare Interpreration wollte ich ausdrücken :wink:


edit:
Checked ist aber auch wirklich begrenzt, fehlende Werte können fast überall auftreten,
auch schon aufgrund beliebiger NullPointerExceptions, falls man nicht besonder frei davon arbeitet

eine SQLException von JDBC könnte das komplette Programm belegen, und meist auch nicht lokal zu bearbeiten sondern wirklich durchzureichen an zentrale Stellen

klingt als würde ohne Checked Exceptions der fehlende Wert unbemerkt ins Programm geraten, aber die Alternative RuntimeException ist ja genauso Abbruch,
maximaler Nachteil dass es gleich an weitaus höhere Stellen geht, dass man ein lokales try/catch vergisst,

das sehe ich dagegen grundsätzlich überall als positiv an, außer vielleicht dass man Ressourcen nicht schließt, finally gleich mit vergisst,
aber Fehlerbehandlung lokal muss erst dann sein, wenn man auch dran denkt und dann da ein try/catch hinhaben will

ach, ich sollte nicht immer zuviel Offtopic auf alles eingehen :wink:

@SlaterB

Das hatte ich schon verstanden. Ich gab nur zu Bedenken das auch bei Verwendung von RuntimeExceptions darüber nachgedacht werden sollte ob sie für das was ausgedrückt werden soll ein adäquates Mittel sind oder ob nicht lieber über die Verwenderschnittstelle kommuniziert werden soll was einen erwarten kann :slight_smile:

(siehe evtl. auch noch Edit zuvor)

nun, wenn man an einer Stelle von RuntimeExceptions statt Checked Exceptions spricht, dann ist Hinweis auf ganz ohne Exceptions ja noch ein weiteres völlig neues Thema,
oder falls du ein nicht zu überlesendes throws von Checked Exceptions meinst, dann war es doch passend

der Schichtaufbau steht gerade bei Exceptions der Kommunikation etwas entgegen, man kann nicht sämliche Aktionen, Exceptions, Problemfälle nach oben weitergeben

Ganz so darf man das nicht sehen. Im Fehlerfall wird wie gewohnt eine Exception geworfen. Das Optional entspricht einem möglichen null als Rückgabewert. Der Vorteil ist aber, dass explizit gemacht ist, dass es auch keinen Rückgabewert geben kann. Ob eine Methode null zurückgeben darf oder nicht, sieht man an der Signatur nicht direkt (natürlich kann man auch @Nullable-Annotationen verwenden, um das explizit zu machen, aber das ist nicht ganz so offensichtlich).

und noch mal erklären, was ich meine:

in der Diskussion um Exceptions Optional hinzubringen klingt/ klann nach Verzicht auf Exception und einfach Weitermachen mit null/ keinem Wert,
ohne Exception-Abbruch, das kann ja keine Alternative sein,
dass Optional unabhängig von Exception-Fragen allgemein für variablen Rückgabewert steht und für sich einen Sinn hat, steht natürlich außer Frage

zum Glück nun gleich und wahrscheinlich ganzes Wochenende weg, da kann ich noch mehr das letzte Wort haben (wollen) :wink:

@SlaterB

Du kannst schon Exceptions haben, aber der Transportweg ist ein anderer. Einerseits kannst du RuntimeExceptions deklarieren, die sind für den Verwender völlig transparent, er merkt erst etwas wenn das Programm explodiert. Vll. schaut er dann in die Dokumentation und wenn er Glück hat findet er die Exceptions beschrieben.

Eine andere Möglichkeit ist die Exceptions über ein spezielles Konstrukt per Rückgabewert zu propagieren. Dieses Konstrukt unterstützt dann genau wie Optional eine Verkettung von Operationen und führt diese nur aus wenn kein Fehler vorliegt. Hier hat man den Vorteil das es über das Typsystem explizit gemacht wird das eine Methode kein gültiges Ergebnis liefern kann.
Bsp. du möchtest irgendwo über ein Medium eine Verbindung öffnen und den Inhalt verarbeiten falls alles gut war. Nehmen wir an das Konstrukt heisst Try, das ähnlich einem Optional funktioniert jedoch entweder einen gültigen Wert besitzt oder einen Fehler (Exception) speichert. Dann könntest du so was tun Try(bar.openConnection()).verkettung(con -> Try(con.getInputStream)). Schlägt das Öffnen fehl wird die Exception im Try zwischengespeichert und der Inpustream nicht mehr geholt. Da der ganze Ausdruck Typen Try ist kann der Verwender im Try nachsehen ob es einen Fehler gab oder ob der Inputstream vorhanden ist. Dann hat er die Wahl, gibt es eine gespeicherte Exception kann er die extrahieren und tatsächlich werfen, weil es vll. ein grober irreparabler Fehler ist oder alternativ er bricht seine Arbeit ab und reicht das Try weiter nach oben in der Hoffnung das dort jmd etwas damit anfangen kann.

Die Art wie der Fehler durchs System propagiert ist einfach ein anderer und wie deutlich es gemacht wird wo, es zu einem Fehler kommen kann.

[QUOTE=ThreadPool]
Eine andere Möglichkeit ist die Exceptions über ein spezielles Konstrukt per Rückgabewert zu propagieren. Dieses Konstrukt unterstützt dann genau wie Optional eine Verkettung von Operationen und führt diese nur aus wenn kein Fehler vorliegt. Hier hat man den Vorteil das es über das Typsystem explizit gemacht wird das eine Methode kein gültiges Ergebnis liefern kann.
Bsp. du möchtest irgendwo über ein Medium eine Verbindung öffnen und den Inhalt verarbeiten falls alles gut war. Nehmen wir an das Konstrukt heisst Try, das ähnlich einem Optional funktioniert jedoch entweder einen gültigen Wert besitzt oder einen Fehler (Exception) speichert. Dann könntest du so was tun Try(bar.openConnection()).verkettung(con -> Try(con.getInputStream)). Schlägt das Öffnen fehl wird die Exception im Try zwischengespeichert und der Inpustream nicht mehr geholt. Da der ganze Ausdruck Typen Try ist kann der Verwender im Try nachsehen ob es einen Fehler gab oder ob der Inputstream vorhanden ist. Dann hat er die Wahl, gibt es eine gespeicherte Exception kann er die extrahieren und tatsächlich werfen, weil es vll. ein grober irreparabler Fehler ist oder alternativ er bricht seine Arbeit ab und reicht das Try weiter nach oben in der Hoffnung das dort jmd etwas damit anfangen kann.

Die Art wie der Fehler durchs System propagiert ist einfach ein anderer und wie deutlich es gemacht wird wo, es zu einem Fehler kommen kann.[/QUOTE]

Etwas Code bei die Fische: https://github.com/bradleyscollins/try4j

@Landei

Hm, das ist mit eine der besseren Implementierungen die im Netz umhergeistern, da wird sich SlaterB freuen etwas zu sehen was man auch benutzen kann. :wink:

flott

edit:
wobei

List<String> results = denominators.stream()
    .map(d -> Try.to(() -> 100 / d))
    .map(t -> {
      return t.transform(
          n -> Success.of(Integer.toString(n)), 
          e -> Success.of("n/a")
      ).get();
    })
    .collect(Collectors.toList());

als Code ziemlich fragwürdig ist, das muss ich in diesem Thread hier ja geradezu zwingend sagen,
um auch wieder zum Thema zurückzukommen, wo auch immer es zwischendurch gewesen ist

bisher ist mir sowas als Standardsprachmittel im normalen Gebrauch noch nirgendwo begegnet,
mal sehen ob es je dazu kommt, Java ist das kaum mehr zu nennen :wink:

[QUOTE=SlaterB]flott

edit:
wobei

List<String> results = denominators.stream()
    .map(d -> Try.to(() -> 100 / d))
    .map(t -> {
      return t.transform(
          n -> Success.of(Integer.toString(n)), 
          e -> Success.of("n/a")
      ).get();
    })
    .collect(Collectors.toList());

als Code ziemlich fragwürdig ist, das muss ich in diesem Thread hier ja geradezu zwingend sagen,
um auch wieder zum Thema zurückzukommen, wo auch immer es zwischendurch gewesen ist

bisher ist mir sowas als Standardsprachmittel im normalen Gebrauch noch nirgendwo begegnet,
mal sehen ob es je dazu kommt, Java ist das kaum mehr zu nennen ;)[/QUOTE]

Ich habe eine eigene, ähnliche Implementierung davon, da könnte man es so schreiben:

List<String> results = denominators.stream()
    .map(d -> Try.to(() -> 100 / d).map(n -> Integer.toString(n)).getOrElse("n/a"))
    .collect(Collectors.toList());

Alles Gewohnheitssache und Geschmacksfrage.