The Answer by cyberbrain is correct, and smart.
Just for fun, here is a rewrite of your app to be more object-oriented. While this may be too much for a beginning student, this is the direction to head for in your learning.
Notice the separation of concerns. We create a separate class to handle each area of your app:
- "business" domain objects (
Event & Sale) - user-interface (
TerminalUserInterface) - execution-lifecycle (
App)
Event & Sale — domain
The Event & Sale classes represent the core of your app, the domain, what your stakeholders really care about when building your software.
These contain data (list of sales, count of tickets remaining) as well as the code that operates upon this data (adding to list of sales, decrementing count of tickets remaining).
Importantly, we could go on to write tests that exercise these classes, before bothering with any user-interface.
package work.basil.example.tickets; public record Sale( String customer , int countTicketsPurchased ) { public Sale { if ( this.countTicketsPurchased ( ) < 0 ) { throw new IllegalArgumentException ( "count tickets purchased cannot be negative" ); } } }
package work.basil.example.tickets; import java.util.ArrayList; import java.util.List; public class Event { public final String name; private final int totalCountTickets; private int remainingTicketCount; private final List < Sale > sales; public final int MAXIMUM_TICKETS_PER_PURCHASE = 2; public Event ( final String name , final int countTicketsToBeSold ) { // Arguments this.name = name; this.totalCountTickets = countTicketsToBeSold; this.remainingTicketCount = this.totalCountTickets; // Other initialization. this.sales = new ArrayList < Sale > ( ); } // Getters public boolean ticketsAvailable ( ) { return remainingTicketCount > 0; } public int countRemainingTickets ( ) { return remainingTicketCount; } // Logic public void addSale ( final Sale theSale ) { if ( theSale.countTicketsPurchased ( ) > MAXIMUM_TICKETS_PER_PURCHASE ) { throw new IllegalArgumentException ( "Too many tickets in purchase requested. Limit:" + MAXIMUM_TICKETS_PER_PURCHASE ); } this.remainingTicketCount = this.remainingTicketCount - theSale.countTicketsPurchased ( ); // Decrement tickets available. this.sales.add ( theSale ); } public String salesReport ( ) { StringBuilder salesReport = new StringBuilder ( ); this.sales.forEach ( salesReport :: append ); return salesReport.toString ( ); } // `Object` Overrides @Override public String toString ( ) { return "Event{" + "name='" + name + '\'' + ", remainingTicketCount=" + remainingTicketCount + ", sales.size()=" + sales.size ( ) + ", MAXIMUM_TICKETS_PER_PURCHASE=" + MAXIMUM_TICKETS_PER_PURCHASE + '}'; } }
TerminalUserInterface — user interface
We move all the code for interacting with the user on the console to this one class. We can focus on that user interaction without thinking about the domain implementation details contained in a separate class.
Notice the use of a helper method makePurchase to break up the code into more manageable chunks. (In the old days, we called that a subroutine.)
package work.basil.example.tickets; import java.time.Instant; import java.util.Objects; import java.util.Scanner; public class TerminalUserInterface { private final Event event; public TerminalUserInterface ( final Event theEvent ) { this.event = Objects.requireNonNull ( theEvent ); } public void run ( ) { final Scanner scanner = new Scanner ( System.in ); while ( event.ticketsAvailable ( ) ) { System.out.println ( "------| Event: " + event.name + " |------" ); if ( ! this.event.ticketsAvailable ( ) ) { System.out.println ( "Event sold out.-" ); break; } System.out.println ( "TICKETS AVAILABLE: " + event.ticketsAvailable ( ) + " … " + this.event.countRemainingTickets ( ) ); System.out.println ( "Make purchase: (y/n): " ); String response = scanner.next ( ).toLowerCase ( ); switch ( response.toLowerCase ( ) ) { case "n" -> { System.out.println ( "Exiting app, per your request." ); return; } case "y" -> { this.makePurchase ( scanner ); } default -> { System.out.println ( "Invalid input." ); return; } } } System.out.println ( "App exiting." + Instant.now ( ) ); } private void makePurchase ( final Scanner scanner ) { // Customer name System.out.println ( "Customer name: " ); String name = scanner.next ( ); if ( name.isBlank ( ) ) { System.out.println ( "Customer name is blank." ); return; } // Count tickets to purchase System.out.println ( "How many tickets to purchase?: " ); int countTicketsToPurchase = scanner.nextInt ( ); // Too few tickets. if ( countTicketsToPurchase <= 0 ) { System.out.println ( "Count of tickets must be a positive integer. Your input: " + countTicketsToPurchase ); return; } // Too many tickets. if ( countTicketsToPurchase > this.event.MAXIMUM_TICKETS_PER_PURCHASE ) { System.out.println ( "Count of tickets limited to: " + this.event.MAXIMUM_TICKETS_PER_PURCHASE + ". Your input: " + countTicketsToPurchase ); return; } Sale sale = new Sale ( name , countTicketsToPurchase ); this.event.addSale ( sale ); System.out.println ( "Purchase executed successfully: " + sale.toString ( ) ); } }
App — execution lifecycle (setup-teardown)
Notice how we set up the needed objects, in this case an Event object, before starting the user-interface.
package work.basil.example.tickets; import java.time.Instant; public class App { public static void main ( String[] args ) { App app = new App ( ); app.demo ( ); } private void demo ( ) { System.out.println ( "INFO - Demo start. " + Instant.now ( ) ); // Setup Event event = new Event ( "Flea Circus" , 12 ); // User-interface TerminalUserInterface tui = new TerminalUserInterface ( event ); tui.run ( ); // Teardown System.out.println ( "DEBUG - event = " + event ); System.out.println ( "INFO - Demo done. " + Instant.now ( ) ); } }
Example run
INFO - Demo start. 2025-02-15T00:22:17.436491Z ------| Event: Flea Circus |------ TICKETS AVAILABLE: true … 12 Make purchase: (y/n): y Customer name: Bubba How many tickets to purchase?: 2 Purchase executed successfully: Sale[customer=Bubba, countTicketsPurchased=2] ------| Event: Flea Circus |------ TICKETS AVAILABLE: true … 10 Make purchase: (y/n): y Customer name: Jean-Pierre How many tickets to purchase?: 1 Purchase executed successfully: Sale[customer=Jean-Pierre, countTicketsPurchased=1] ------| Event: Flea Circus |------ TICKETS AVAILABLE: true … 9 Make purchase: (y/n): n Exiting app, per your request. DEBUG - event = Event{name='Flea Circus', remainingTicketCount=9, sales.size()=2, MAXIMUM_TICKETS_PER_PURCHASE=2} INFO - Demo done. 2025-02-15T00:22:59.300977Z
BuyerandTicketand maintain aMap<Buyer, Ticket>. Also start withList<Ticket>and subtract from it