Start learning free

LESSON 561 OF 569 · OBJECT-ORIENTED PROGRAMMING - CHALLENGES

Payment Fees in Java: Abstract Classes and Abstract Methods

Practice O(1) Read the full explanation

A shop takes payments by card, by PayPal and in cash. Every payment has an amount, in cents, and a total: the amount plus a processing fee. The total is worked out the same way for every payment; only the fee depends on the kind of payment. That is what an abstract class is for: it holds the shared part, says that every payment has a fee, and leaves the fee itself to each kind of payment.

  • The abstract class Payment is built from an amount, in cents, and keeps it. Its method fee() is abstract: every payment has a fee, but Payment can't say what it is. Its method total() is concrete: it returns the amount plus fee().
  • Card, PayPal and Cash inherit from Payment, keep its total() as it is, and each writes its own fee():
  • Card pays 2%: its fee is amount × 2 / 100.
  • PayPal pays 3% plus 30 cents: its fee is amount × 3 / 100 + 30.
  • Cash costs nothing: its fee is 0.

In Java, Payment is an abstract class and fee is declared public abstract int fee();, with no body. Each subclass extends Payment, passes its amount to Payment's constructor with super(amount), and overrides fee. Dividing two ints drops the fraction.

Every division rounds down: a card payment of 1999 cents has a fee of 39, not 39.98.

The starter code already has every class, constructor and method header: declare the field and the abstract method, and write the bodies. For each test, the harness creates the payments in the order given and prints two lines: each payment's total, total(), separated by spaces, and then the sum of all the fees. It calls them through the abstract type Payment, never through Card, PayPal or Cash.

 

Example 1:

Input:
    new Card(1999)
    new PayPal(500)
    new Cash(700)
Output:
    2038 545 700
    84
Explanation: The card's fee is 1999 × 2 / 100 = 39 (39.98 rounded down), so its total is 2038. PayPal's fee is 500 × 3 / 100 + 30 = 45, total 545. Cash has no fee: total 700. The fees add up to 39 + 45 + 0 = 84.

Example 2:

Input:
    new Card(49)
    new PayPal(33)
    new Card(50)
Output:
    49 63 51
    31
Explanation: 49 × 2 / 100 is 0.98, rounded down to 0. PayPal's 33 × 3 / 100 is 0.99, rounded down to 0, plus 30. The second card's 50 × 2 / 100 is exactly 1. The fees are 0 + 30 + 1 = 31.

Example 3:

Input:
    new Card(0)
    new PayPal(0)
    new Cash(0)
Output:
    0 30 0
    30
Explanation: Amounts can be 0. PayPal still adds its 30 cents.

 

Constraints:

  • Every test has between 1 and 100 payments.
  • Every amount is an integer from 0 to 106 cents.

Note:

Every fee and every total takes O(1) time.

STEP 1 OF 10
    Ctrl + Enter to check

    JAVA · OBJECT-ORIENTED PROGRAMMING - CHALLENGES

    Payment Fees in Java, explained

    Problem overview

    A shop takes payments by card, by PayPal and in cash. Every payment has an amount in cents, and a total() that is the amount plus a processing fee. The total is worked out the same way for every payment. Only the fee depends on the kind.

    That is what an abstract class is for. Payment holds the shared part (the amount and total()) and declares that every payment has a fee(), but it can't say what the fee is. Each subclass supplies its own:

    ClassFee
    Cardamount * 2 / 100 (2%)
    PayPalamount * 3 / 100 + 30 (3% plus 30 cents)
    Cash0

    For Card(1999) the fee is 39, because the division drops the fraction (39.98 becomes 39), and the total is 2038. For PayPal(500) the fee is 45 and the total 545. For Cash(700) the total is 700. The three fees add up to 84, which is the second line the tests print for the first example.

    Abstract classes in Java

    A class marked abstract can't be created with new. It exists to be extended. It can have fields, constructors and ordinary methods like any class, and it can also declare abstract methods: a header ending in a semicolon, with no body, such as public abstract int fee();. Every concrete subclass must override each abstract method, or the compiler refuses it.

    The constructor of an abstract class still matters. It runs when a subclass object is created, and the subclass starts it with super(amount), which is how the amount reaches the shared field.

    The key line is in total(): it calls fee(), and Java picks the version that belongs to the object. For a Card that is the card's fee, even though total() is written only once in Payment.

    You could ask why fee() isn't an ordinary method in Payment that returns 0. Then a subclass that forgot to write its own fee would compile and quietly charge nothing. With an abstract method, the compiler forces every kind of payment to decide its fee, and it makes a plain Payment impossible to create, which is right: there is no such thing as a payment of no particular kind.

    Step by step

    1. Declare the shared field in Payment: public int amount;. Card, PayPal and Cash all inherit it.
    2. Store it in the constructor with this.amount = amount;.
    3. Declare fee() as abstract: public abstract int fee();, with a semicolon instead of a body.
    4. Write total() as amount + fee().
    5. Pass the amount up in each subclass constructor with super(amount);.
    6. Override fee() in each subclass: amount * 2 / 100 for a card, amount * 3 / 100 + 30 for PayPal and 0 for cash.

    The solution in Java

    abstract class Payment {
        public int amount;
    
        public Payment(int amount) {
            this.amount = amount;
        }
    
        public abstract int fee();
    
        public int total() {
            return amount + fee();
        }
    }
    
    class Card extends Payment {
        public Card(int amount) {
            super(amount);
        }
    
        @Override
        public int fee() {
            return amount * 2 / 100;
        }
    }
    
    class PayPal extends Payment {
        public PayPal(int amount) {
            super(amount);
        }
    
        @Override
        public int fee() {
            return amount * 3 / 100 + 30;
        }
    }
    
    class Cash extends Payment {
        public Cash(int amount) {
            super(amount);
        }
    
        @Override
        public int fee() {
            return 0;
        }
    }

    Integer division and the order of operations

    All the values are int, so / drops the fraction. Multiply first and divide last: amount * 2 / 100 is evaluated from left to right, so it is (amount * 2) / 100. For an amount of 150 that is 3. Dividing first, as in amount / 100 * 2, gives 1 * 2 = 2, because the division loses the 50 too early.

    For PayPal the 30 is added after the division. With an amount of 33, 33 * 3 / 100 is 0, so the fee is exactly 30. With an amount of 0 it is still 30.

    Why the amounts stay safe

    The amount is at most 1,000,000 cents, so amount * 3 is at most 3,000,000, far below the largest int (2,147,483,647). A Card(1000000) totals 1020000 and a PayPal(1000000) totals 1030030.

    Using the payments through the abstract type

    Because every payment is a Payment, one array can hold all the kinds, and the code that uses them never names Card, PayPal or Cash:

    Payment[] payments = { new Card(1999), new PayPal(500), new Cash(700) };
    int fees = 0;
    for (Payment payment : payments) {
        System.out.println(payment.total());
        fees += payment.fee();
    }
    System.out.println("fees: " + fees);
    
    2038
    545
    700
    fees: 84
    

    A fourth kind of payment, say a bank transfer with a flat fee, only needs a new subclass with its own fee(). total() and this loop stay as they are.

    Testing

    PaymentsTotalsSum of fees
    Card(1999), PayPal(500), Cash(700)2038 545 70084
    Card(49), PayPal(33), Card(50)49 63 5131
    Card(0), PayPal(0), Cash(0)0 30 030

    In the second row, 49 cents on a card is 0.98 cents of fee, which rounds down to 0, and 50 cents is exactly 1.

    Common mistakes

    • Trying to create a plain Payment. new Payment(100) is a compile error: Payment is abstract; cannot be instantiated.
    • Leaving fee() out of a subclass. A class such as Check extends Payment that doesn't override it fails to compile with Check is not abstract and does not override abstract method fee() in Payment. Cash has no fee, but it still has to write fee() and return 0.
    • Forgetting super(amount). Payment has only a constructor that takes an amount, so every subclass constructor must call it. Without the call, the subclass doesn't compile.
    • Dividing before multiplying. amount / 100 * 2 gives 2 for an amount of 150, while the correct fee is 3.
    • Overriding total(). The lesson keeps total() in Payment only, so that the total is computed in one place.
    • Forgetting the 30 cents at amount 0. PayPal's fee is not a percentage alone; the 30 is always added.

    Complexity

    Each fee() and each total() is a few arithmetic operations, so it takes O(1) time and no extra space.

    Keep going with Java500+ step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free