Stückelung von Geldscheinen

JavaLernen1

Mitglied
Guten Abend!
Ich habe einen Code geschrieben, der Folgendes tut: Es wird nach einem Geldbetrag in Euro gefragt und dann soll ausgegeben werden, wie dieser in Euroscheinen gestückelt wird; wenn der Betrag nicht in Scheine gestückelt werden kann, wird eine Meldung ausgegeben, dass ein neuer Betrag eingegeben werden soll.

Hier mein Code:
Java:
import java.util.Scanner;

public class Geldausgabe {

    public static void main(String[] args) {

        System.out.println("Bitte Betrag in EUR eingeben: ");
        Scanner scanner1 = new Scanner(System.in);
        int wunschbetrag = scanner1.nextInt();
        geldAusgabe(wunschbetrag);
        scanner1.close();
    }

    public static void geldAusgabe(int n) {

        int geldwunsch = n;
        int[] scheine = { 500, 200, 100, 50, 20, 10, 5 };
      
        if (geldwunsch % 5 == 0) {

            for (int j = 0; j < scheine.length; j++) {
                int anzahl = (geldwunsch / scheine[j]);
                geldwunsch = geldwunsch - anzahl * scheine[j];
                System.out.printf("Anzahl %d-Schein: %d%n", scheine[j], anzahl);
            }

        } else {
            System.out.println("Betrag in Scheinen nicht auszahlbar, bitte neuen Betrag eingeben: ");
            Scanner scanner2 = new Scanner(System.in);
            int wunschbetrag = scanner2.nextInt();
            geldAusgabe(wunschbetrag);
            scanner2.close();
        }

    }
}

Sicher tut der Code, was er soll. Aber meine Frage ist, ob es einen eleganteren Weg gibt (und den gibt es ganz bestimmt).

Beispielsweise habe ich versucht, mit einer while-Schlife zu arbeiten; das sieht dann, wie ich finde, schon übersichtlicher aus:

Java:
import java.util.Scanner;

public class AusgabeGeld {

    public static void main(String[] args) {
       
        System.out.println("Bitte Betrag in EUR eingeben: ");
        Scanner sc = new Scanner(System.in);
        int wunschwert = sc.nextInt();
       
        while(wunschwert % 5 != 0) {
            System.out.println("Bitte neuen Betrag eingeben: ");
            sc = new Scanner(System.in);
            wunschwert = sc.nextInt();
        }
       
        geldAusgabe(wunschwert);
        sc.close();
       
    }
   
    public static void geldAusgabe(int n) {

        int geldwunsch = n;
        int[] scheine = { 500, 200, 100, 50, 20, 10, 5 };
       
        for (int j = 0; j < scheine.length; j++) {
                int anzahl = (geldwunsch / scheine[j]);
                geldwunsch = geldwunsch - anzahl * scheine[j];
                System.out.printf("Anzahl %d-Schein: %d%n", scheine[j], anzahl);
            }
      }
}
 
Zuletzt bearbeitet:

Oneixee5

Top Contributor
Zuerst: einen Scanner von System.in sollte man nicht schließen. Was erwartest du was dann passieren soll, dein Computer implodiert oder so.
Du musst auch nicht immer einen neuen Scanner öffnen, du kannst ihn immer wieder verwenden.
 

JavaLernen1

Mitglied
Zuerst: einen Scanner von System.in sollte man nicht schließen. Was erwartest du was dann passieren soll, dein Computer implodiert oder so.
Du musst auch nicht immer einen neuen Scanner öffnen, du kannst ihn immer wieder verwenden.
Danke für dein Feedback.
Zu dem Scanner habe ich eine Frage.

Wenn ich einen Scanner zum Einlesen von System.in erstellt habe,

Scanner sc = new Scanner(System.in);

und später will ich etwas Neues über System.in einlesen, wie kann ich dann denselben Scanner wiederverwenden?

Achja, zu dem Schließen des Scanners: Da gibt mir Eclipse einen Warnhinweis, wenn ich ihn nicht schließe.
 

KonradN

Super-Moderator
Mitarbeiter
Ich will mal versuchen, die einzelnen Punkte etwas zu klären:
Achja, zu dem Schließen des Scanners: Da gibt mir Eclipse einen Warnhinweis, wenn ich ihn nicht schließe.
Die Problematik ist hier relativ einfach:
  • Eclipse schaut, dass Instanzen, die Du im Code erzeugst und die AutoClosable implementieren, auch geschlossen werden. Das ist etwas, das auch in der Regel genau so sinnvoll ist. Typische Dinge sind hier so Öffnen einer Datei oder so ... die sollte man dann auch schließen.
  • Scanner implementiert AutoClosable. Damit fällt eine Scanner Instanz in diese Regel.
  • Die Problematik beim Schließen kommt aber jetzt: Wenn Du einen Scanner auf einem Stream öffnest und dann den Scanner schließt, dann wird auch der zugrunde liegende Stream geschlossen. Wenn Du also den Scanner auf System.in schließt, dann schließt Du damit auch System.in
  • Dieses Schließen hat zwei Probleme: A) Etwas, das Dir nicht gehört hast Du nicht zu schließen. Du hast System.in nicht geöffnet, daher hast Du es nur zu nutzen aber nicht zu schließen. B) Wenn System.in einmal geschlossen ist, dann kannst Du da nichts mehr einlesen.

wie kann ich dann denselben Scanner wiederverwenden?
Das ist wie bei allen Dingen: Wenn Du es an mehreren Stellen nutzen willst, dann musst Du es so speichern, dass Du da von allen gewünschten Stellen Zugriff hast. Da kommt dann das Verständnis der Variablen in Spiel:
  • eine lokale Variable existiert nur in Ihrem Block. Wenn Du etwas außerhalb nutzen willst, dann taugt das nicht.
  • Instanzvariablen sind in der Instanz deklariert und man kann von allen Elementen einer Instanz darauf zugreifen. Je nach Sichtbarkeit auch von außerhalb. Aber man braucht dann immer eine Instanz.
  • Klassenvariablen gelten Klassenweit. Das ist das, was Du hier nutzen kannst:
Java:
import java.util.Scanner;

public class AusgabeGeld {
    static Scanner scanner = new Scanner(System.in);
    
    // ...
}
Auf diese Variable scanner kannst Du in allen deinen static Methoden zugreifen.
 

KonradN

Super-Moderator
Mitarbeiter
Bezüglich Deines Codes noch ein paar Hinweise. Dein Code war ja:
Java:
    public static void geldAusgabe(int n) {

        int geldwunsch = n;
        int[] scheine = { 500, 200, 100, 50, 20, 10, 5 };
      
        for (int j = 0; j < scheine.length; j++) {
                int anzahl = (geldwunsch / scheine[j]);
                geldwunsch = geldwunsch - anzahl * scheine[j];
                System.out.printf("Anzahl %d-Schein: %d%n", scheine[j], anzahl);
            }
      }

Was ich immer sehr wichtig finde sind ordentliche Bezeichner. Wenn ich mir nur die Methodensignatur ansehe, dann frage ich mich: Was soll denn n sein?

"n" sagt nichts aus. Was steckt denn da drin? Das ist doch der auszuzahlende Geldbetrag. Sollte also etwas in der Art sein.
"geldAusgabe" ist schon besser, aber auch da könnte man sich mehr Gedanken machen. Denn Geld Ausgeben kann man auch 2 Euro. Das geht aber bei Deiner Methode nicht wirklich.
"geldwunsch" - das ist irreführend. Der Geldwunsch ändert sich ja nicht. Jemand will 150€ ... Nur durch das abzählen von Geld wird mein Geldwunsch nicht geringer. Das ist also eher etwas wie Restgeld.
"scheine" - wieder mehr verständlich, aber das sind ja mehr Geldschein Beträge ...
"j" - i und j sind zwar typische Zählvariablen, aber da neige ich eher auch dazu, richtige Begriffe zu nutzen.

Wenn man dann eine Schleife hat, die alle Elemente eines Arrays durchgeht - da ist dann die for each Schleife gut geeignet. Das wäre dann etwas wie:
Java:
    public static void geldAusgabe(int wunschBetrag) {

        int restGeld = wunschBetrag;
        int[] geldscheinGroessen = { 500, 200, 100, 50, 20, 10, 5 };

        for (int groesse : geldscheinGroessen) {
            int anzahl = (restGeld / groesse);
            restGeld = restGeld - anzahl * groesse;
            System.out.printf("Anzahl %d-Schein: %d%n", groesse, anzahl);
        }
    }

Und dann muss man immer die Funktionalität prüfen. Jetzt bekommt man zu wenig, wenn man keinen Betrag übergibt, der durch 5 teilbar ist. Das hast Du ja in einer anderen Methode geprüft. Aber das kann zu Problemen führen. Daher ist wichtig, immer Prüfungen einzubauen. Das kann etwas sein wie:
if (wunschBetrag % 5 != 0) throw new IllegalArgumentException("wunschBetrag muss durch 5 teilbar sein!");
(das man direkt an den Anfang der Methode schreiben würde)

Das wäre dann einfach einmal eine kleine Rückmeldung zu dem Code selbst.
 

JavaLernen1

Mitglied
  • Klassenvariablen gelten Klassenweit. Das ist das, was Du hier nutzen kannst:
Java:
import java.util.Scanner;

public class AusgabeGeld {
    static Scanner scanner = new Scanner(System.in);
   
    // ...
}
Auf diese Variable scanner kannst Du in allen deinen static Methoden zugreifen.
Wenn ich später im Code also eine neue Eingabe über System.in habe, dann kann ich also wieder sowas wie

Java:
System.out.println("Neue Eingabe: ");
int neu = sc.nextInt();

schreiben und sc nimmt wieder die Eingabe auf?
 

Neumi5694

Top Contributor
Zum Algorithmus selbst:
Falls du nach der Bildschirmausgabe noch den Auswurfmechanismus ansprechen wolltest, müsstest du in der aktuellen Form die Berechnung nochmal starten.
Also: Als erstes mal würde ich die Ausgabe von der Logik trennen. Das Ergebnis einer Ausgabe sollte immer nach Abschluss der Aufgabe ausgegeben werden und weiterverwendbar sein, also keine System.outs während der Berechnung (nur zum Debuggen, und auch da würde ich einen Logger verwenden, System.out im Code ist elendig schwer wiederzufinden, wenn man erst mal 1000 Klassen hat.).

Außerdem würde ich die Werte der Scheine durch ein Enum namens Schein ersetzen das als eine Eigenschaft den Wert enthält, schließlich handelt es sich um eine fest definierte Menge von auswählbaren Objekten.
Java:
enum Schein {
    Fuenfhunderter(500), ...
}
Vorteil: Typensicher, bei einem int weiß man nie. Außerdem kannst du ihnen so noch mehr Eigenschaften zuweisen, falls notwendig.
Nachteil: Wert mit getWert() abfragen, etwas mehr Tipparbeite, aber insgesamt besser lesbar und wartbar.

In der Methode erstellst du dann als erstes mal eine EnumMap für die Scheine, die du befüllst, die Rückgabe ist eine Map aus Map<Schein, int>.
Achte darauf, dass alle Werte gesetzt sind, initialisiere sie daher am Besten vor der Ausführung mit
Java:
Arrays.stream(Schein.values()).forEach(map.put(Schein,0)); //anstatt dem Stream geht natürlich auch for each
Alternativ zur Map kannst du natürlich auch eine Liste einer passenden Klasse erstellen und weglassen, was keine Anzahl hat.
 

KonradN

Super-Moderator
Mitarbeiter
Als erstes mal würde ich die Ausgabe von der Logik trennen.
@Neumi5694 hat das in Kürze recht schön erwähnt. Ich hatte es erst einmal noch weggelassen, da ich vermute, dass du noch ganz weit am Anfang bist und das dann zu viel wäre. Aber da es angesprochen wurde, möchte ich hier einmal die Chance ergreifen und es noch etwas ausführen.

Ein ganz wichtiges Thema ist die Lesbarkeit vom Code. Nur wenn Code lesbar ist, kann man ihn zukünftig vernünftig warten. (In der Praxis schreibt man ja nicht ein kleines Programm, testet es und spielt damit herum um dann sozusagen zum nächsten Kapitel weiter zu gehen. So Software lebt ja eine gewisse Zeit und es sollen ständig neue Features dazu kommen und so ...

Ein sehr gutes Mittel ist dabei, kleine Methoden zu haben, die genau sagen, was diese tun. Dein Programm macht ja folgendes:

1. Frage Benutzer nach der Geldsumme
2. Wiederhole Schritt 1 so lange Geldsumme nicht in scheinen auszahlbar ist
3. Berechne Auszuzahlende Scheine
4. Gib auszuzahlende Scheine aus

Diese Anleitung kann man sehr gut verstehen. Und das kann man so fast 1:1 in Code schreiben:

Java:
int auszuzahlendesGeld;
do {
    auszuzahlendesGeld = frageAnwenderNachGeldbetrag();
} while (istBetragInScheinenAuszahlbar(auszuzahlendesGeld));
var auszuzahlendeGeldscheine = calkuliereAuszuzahlendeGeldscheine(auszuzahlendesGeld);
gibtAuszuzahlendeGeldscheineAus(auszuzahlendeGeldscheine);

Das ist direkt verständlich. wobei man noch die Scheife in eine eigene Methode packen könnte und dann hätte man nur:
Java:
int auszuzahlendesGeld = frageAnwenderNachInScheinenAuszuzahlendemGeldbetrag();
var auszuzahlendeGeldscheine = calkuliereAuszuzahlendeGeldscheine(auszuzahlendesGeld);
gibtAuszuzahlendeGeldscheineAus(auszuzahlendeGeldscheine);

und natürlich:
Code:
public static int () {
    int auszuzahlendesGeld;
    do {
        auszuzahlendesGeld = frageAnwenderNachGeldbetrag();
    } while (istBetragInScheinenAuszahlbar(auszuzahlendesGeld));
    return auszuzahlendesGeld;
}
(Einfach mal 1:1 übernommen. die Methode würde ich noch etwas anders schreiben, aber das ist hier erst einmal nebensächlich.)

Was wir jetzt als kleines Problem haben ist dieses var auszuzahlendeGeldscheine. Wir brauchen hier einen Datentyp, der verwendet werden kann. Da Java eine Objektorientierte Sprache ist, würde man vermutlich objektorientiert heran gehen. Jetzt könnte es aber auch erst einmal ein int[] sein.

Die Geldscheine, die zur Verfügung stehen, müssten dann natürlich auch bekannt sein. Diese lokale Variable müsste dann eine Klassenvariable werden.

Das war jetzt schon einiges, das auf den ersten Blick nichts mit der Trennung von Berechnung und Ausgabe zu tun hat. Wieso macht das Sinn? Warum sollte man das machen?

Hier kommen mehrere Aspekte ins Spiel:

a) Der Name einer Methode soll ja genau beschreiben, was diese Methode macht. Das ist also entgegengesetzt wie im echten Leben: Wenn man Dir sagt: "Bring den Müll raus" wird teilweise erwartet, dass du auf dem Rückweg den Briefkasten leer machst. Aber wenn man sowas implementiert, dann macht die Methode muellRausbringen() nur genau das. Und vom Namen her wird da nichts anderes erwartet.
Das gilt dann auch für eine Methode geldstueckelungAusgeben - diese sollte nur die Geldstückelung ausgeben. Das bedeutet aber auch: Es wird nichts berechnet! Die Geldstückelung muss also schon existieren!

b) Im anderen Post ist es schon angeklungen: Du brauchst sowas ggf. öfter. Du willst sowas ja nicht mehrfach implementieren: Einmal bei der Bildschirmausgabe und einmal bei der eigentlichen Geldausgabe oder so ... Daher ist so eine Unterteilung hilfreich bei der Verwendung von Methoden.

c) "Separation of Concerns" - Es macht immer Sinn, die eigentlichen Anliegen zu trennen. Du hast jetzt nur die Ausgabe auf der Konsole, aber es kann ja viele unterschiedliche Möglichkeiten geben. Daher macht hier eine Trennung Sinn. Dann hast Du Klassen, die nur das sogenannte Model abbilden. Du hast Geldscheine und Klassen, die eine Geldstückelung berechnen können u.s.w.
Das ist universell einsetzbar. Evtl. brauchst Du es als eine Applikation, die auf einem Kassensystem läuft und das der Kassiererin auf dem Bildschirm klar aufzeigt, welchen Schein sie wie oft herausgeben muss. (Also mit Grafik und so!) Oder es ist eine Web-Applikation. Oder die Daten werden nur als Daten weiter gegeben, damit eine Ausgabeeinheit beim Geldautomat dann das Geld abzählen und ausgeben kann.
==> Egal was kommt: Du hast das Model einmal geschrieben und kannst es überall einsetzen.
Und der große Nebeneffekt: Durch diese Aufteilung wird der Code in jeder Klasse deutlich geringer. Man muss nicht zu viel in eine Klasse packen.

Das wäre dann mal eine etwas länger Erläuterung zu dem Punkt.
 

Neue Themen


Oben