Chapter:Exceptions - Creating your own - Exercises

From Juneday education
Jump to: navigation, search

Exercises

1 - Create a checked exception

Create a new class called InsufficientBalanceException which extends Exception. Implement at least one constructor which takes a reference to a String as an argument, and calls the superclass (Exception) constructor, passing the String on.

Example constructor:

public InsufficientBalanceException(String message){
  super(message);
}

Change the withdraw method of the SavingsAccount class below, so that it declares that it throws InsufficientBalanceException if the balance doesn't cover the attempted withdrawal:

Information
In real life, we'd use java.math.BigDecimal for money, and not double
public class SavingsAccount{
  private double balance;
  public SavingsAccount(){

  }
  public SavingsAccount(double initialBalance){
    this.balance = initialBalance;
  }

  public void withdraw(double amount){
    if( (balance-amount) >=0.0 ){
      balance -= amount;
    }else{
      ; // throw a new InsufficientBalanceException
        // with a message including an error message and
        // the current balance, and the amount requested
    }
  }
  public void deposit(double amount){
    balance += amount;
  }
  public double balance(){
    return balance;
  }
  @Override
  public String toString(){
    return String.format("Balance: %.2f", balance);
  }
}

You should now have two source code files in the directory where you are working, SavingsAccount.java and InsufficientBalanceException.java . Add a third source code file with a main class which tests the SavingsAccount class. In main, create a SavingsAccount with an initial balance of 100.0. Withdraw 50.00 and print the account to the standard out. Withdraw 50.01 and print the account to standard out. If you couldn't withdraw the second time, your code works.

In the catch clause of the main's try-catch statement, do something like this:

System.err.println("Couldn't withdraw: " + e.getMessage());

An example output from a testrun could look something like this:

$ javac TestSavingsAccount.java && java TestSavingsAccount
Before withdrawal: Balance: 100.00
Withdraw 50.00
After withdrawal: Balance: 50.00
Withdraw 50.01
Couldn't withdraw: Current balance: 50.0 requested withdrawal amount: 50.01
Final state of a: Balance: 50.00

Expand using link to the right to see a suggested answer/solution.

Actually, see the answer to this as part of the answer to the next question!

2 - Change the constructor

SavingsAccount class diagram with two exceptions

Change the constructor of SavingsAccount so that if someone tries to create a new SavingsAccount with a negative balance, the constructor throws IllegalArgumentException with a message about what was wrong.

As the last statement of the test class' main method, try to call the constructor of SavingsAccount with a value of -1. The test output should now end with the following stacktrace (but with file names and line numbers matching your situation):

Exception in thread "main" java.lang.IllegalArgumentException: Negative amount: -1.0
	at SavingsAccount.<init>(SavingsAccount.java:8)
	at TestSavingsAccount.main(TestSavingsAccount.java:17)

IllegalArgument is a runtime exception and you don't have to handle-or-declare the fact that one of the constructors might throw such an exception. We can argue that trying to create an account with a negative initial balance is a programmer error, so a runtime exception should be the way to go here.

Expand using link to the right to see a suggested answer/solution.

Combined answer to Q1 and Q2.

SavingsAccount:

public class SavingsAccount{
  private double balance;
  public SavingsAccount(){

  }
  public SavingsAccount(double initialBalance){
    if(initialBalance<0.0){
      throw new IllegalArgumentException("Negative amount: " + initialBalance);
    }
    this.balance = initialBalance;
  }

  public void withdraw(double amount) throws InsufficientBalanceException{
    if( (balance-amount) >=0.0 ){
      balance -= amount;
    }else{
      throw new InsufficientBalanceException("Current balance: " +
                                             balance +
                                             " requested withdrawal amount: " +
                                             amount);
    }
  }
  public void deposit(double amount){
    balance += amount;
  }
  public double balance(){
    return balance;
  }
  @Override
  public String toString(){
    return String.format("Balance: %.2f", balance);
  }
}

InsufficientBalanceException:

public class InsufficientBalanceException extends Exception{
  public InsufficientBalanceException(){
    super();
  }
  public InsufficientBalanceException(String message){
    super(message);
  }
}

TestSavingsAccount:

public class TestSavingsAccount{
  public static void main(String[] args){
    SavingsAccount a = new SavingsAccount(100.0);
    try{
      System.out.println("Before withdrawal: " + a);
      System.out.println("Withdraw 50.00");
      a.withdraw(50.00);
      System.out.println("After withdrawal: " + a);
      System.out.println("Withdraw 50.01");
      a.withdraw(50.01);
      System.out.println("After withdrawal: " + a);
    }catch(InsufficientBalanceException e){
      System.err.println("Couldn't withdraw: " + e.getMessage());
    }
    System.out.println("Final state of a: " + a);

    new SavingsAccount(-1);
  }
}

Is IllegalArgumentException used in real life? We'll, in the Java API, it is used quite a lot.

3 - Optional exercise

Rather than throwing an IllegalArgumentException, the constructor could throw a custom runtime exception. Create a class NegativeAmountException which extends RuntimeException. Make sure you have at least one constructor which can take a String argument with the message, and which calls the superclass constructor passing on this message String.

Change the constructor of SavingsAccount so that it throws this new type of runtime exception instead. Also, change deposit so that it too throws this new NegativeAmountException if someone tries to deposit a negative amount. Do the same with withdraw, if someone calles withdraw with a negative argument, throw a NegativeAmountException.

No throws declarations should be necessary, since we are throwing a runtime exception here.

Add code to the test class which tries to

  1. create an account with a negative initial amount
  2. deposit a negative amount
  3. withdraw a negative amount

Think about what the effect would be if we allow negative amounts in all three places. Can you explain the effect each situation would have if we allowed negative amounts?

Hint: You need try-catch around each test case which might throw an exception, if you want to try them all

The output could look something like this:

Before withdrawal: Balance: 100.00
Withdraw 50.00
After withdrawal: Balance: 50.00
Withdraw 50.01
Couldn't withdraw: Current balance: 50.0 requested withdrawal amount: 50.01
Creating account with a negative amount
Negative amount: -1.0
Deposit of a negative amount
Can't deposit a negative amount: -1.0
Withdrawal of a negative amount
Can't withdraw a negative amount: -1.0
Final state of a: Balance: 50.00

Expand using link to the right to see a suggested answer/solution.

NegativeAmountException:

public class NegativeAmountException extends RuntimeException{
  public NegativeAmountException(){
    super(); // would be implicit, but we'll write it to remind you
  }
  public NegativeAmountException(String message){
    super(message);
  }
}

SavingsAccount:

public class SavingsAccount{
  private double balance;
  public SavingsAccount(){
    
  }
  public SavingsAccount(double initialBalance){
    if(initialBalance<0.0){
      throw new NegativeAmountException("Negative amount: " + initialBalance);
    }
    this.balance = initialBalance;
  }

  public void withdraw(double amount) throws InsufficientBalanceException{
    if(amount < 0.0){
      throw new NegativeAmountException("Can't withdraw a negative amount: " + amount);
    }
    if( (balance-amount) >=0.0 ){
      balance -= amount;
    }else{
      throw new InsufficientBalanceException("Current balance: " +
                                             balance +
                                             " requested withdrawal amount: " +
                                             amount);
    }
  }
  public void deposit(double amount){
    if(amount < 0.0){
      throw new NegativeAmountException("Can't deposit a negative amount: " + amount);
    }
    balance += amount;
  }
  public double balance(){
    return balance;
  }
  @Override
  public String toString(){
    return String.format("Balance: %.2f", balance); 
  }
}

TestSavingsAccount:

public class TestSavingsAccount{
  public static void main(String[] args){
    SavingsAccount a = new SavingsAccount(100.0);
    try{
      System.out.println("Before withdrawal: " + a);
      System.out.println("Withdraw 50.00");
      a.withdraw(50.00);
      System.out.println("After withdrawal: " + a);
      System.out.println("Withdraw 50.01");
      a.withdraw(50.01);
      System.out.println("After withdrawal: " + a);
    }catch(InsufficientBalanceException e){
      System.err.println("Couldn't withdraw: " + e.getMessage());
    }
    try{
      System.out.println("Creating account with a negative amount");
      new SavingsAccount(-1);
    }catch(Exception e){
      System.err.println(e.getMessage());
    }
    try{
      System.out.println("Deposit of a negative amount");
      a.deposit(-1);
    }catch(Exception e){
      System.err.println(e.getMessage());
    }
    try{
      System.out.println("Withdrawal of a negative amount");
      a.withdraw(-1);
    }catch(Exception e){
      System.err.println(e.getMessage());
    }
    System.out.println("Final state of a: " + a);
  }
}

Bonus - Example using BigDecimal

Here's a small example of how to use BigDecimal (which should be used for important numbers like money):

import java.math.BigDecimal;

public class SavingsAccount{
  private BigDecimal balance;
  public SavingsAccount(){

  }
  public SavingsAccount(BigDecimal initialBalance){
    if(initialBalance.compareTo(BigDecimal.ZERO) < 0){
      throw new NegativeAmountException("Negative amount: " + initialBalance);
    }
    this.balance = initialBalance;
  }

  public void withdraw(BigDecimal amount) throws InsufficientBalanceException{
    if(amount.compareTo(BigDecimal.ZERO) < 0){
      throw new NegativeAmountException("Can't withdraw a negative amount: " + amount);
    }
    if( (balance.subtract(amount)).compareTo(BigDecimal.ZERO) >= 0){
      balance = balance.subtract(amount);
    }else{
      throw new InsufficientBalanceException("Current balance: " +
                                             balance +
                                             " requested withdrawal amount: " +
                                             amount);
    }
  }

  public void deposit(BigDecimal amount){
    if(amount.compareTo(BigDecimal.ZERO) < 0){
      throw new NegativeAmountException("Can't deposit a negative amount: " + amount);
    }
    balance = balance.add(amount);
  }
  public BigDecimal balance(){
    return balance;
  }
    @Override
    public String toString(){
      return String.format("Balance: %.2f", balance);
    }
}

Note that BigDecimal is immutable (just like e.g. String). So you need to do like this, in order to change the balance:

  public void deposit(BigDecimal amount){
    if(amount.compareTo(BigDecimal.ZERO) < 0){
      throw new NegativeAmountException("Can't deposit a negative amount: " + amount);
    }
    balance = balance.add(amount);
  }

Here's a snippet from the client code (client code is what we call code using a class, in our case the main method in a test class):

    SavingsAccount a = new SavingsAccount(BigDecimal.valueOf(100.0));
    try{
      System.out.println("Before withdrawal: " + a);
      System.out.println("Withdraw 50.00");
      a.withdraw(BigDecimal.valueOf(50.00));
      System.out.println("After withdrawal: " + a);
      System.out.println("Withdraw 50.01");
      a.withdraw(BigDecimal.valueOf(50.01));
      System.out.println("After withdrawal: " + a);
    }catch(InsufficientBalanceException e){
      System.err.println("Couldn't withdraw: " + e.getMessage());
    }
    try{
      System.out.println("Creating account with a negative amount");
      new SavingsAccount(BigDecimal.valueOf(-1));
    }catch(Exception e){
      System.err.println(e.getMessage());
    }
    try{
      System.out.println("Deposit of a negative amount");
      a.deposit(BigDecimal.valueOf(-1));
    }catch(Exception e){
      System.err.println(e.getMessage());
    }
    try{
      System.out.println("Withdrawal of a negative amount");
      a.withdraw(BigDecimal.valueOf(-1));
    }catch(Exception e){
      System.err.println(e.getMessage());
    }
    System.out.println("Depositing 50.01");
    a.deposit(BigDecimal.valueOf(50.01));
    System.out.println("Final state of a: " + a);

Links

Further reading

Source code

The source code for all solutions to "writing your own" including the final version with the optional exercise can be downloaded from github.

Where to go next

The next page is: Exceptions_-_Rules_and_syntax

« PreviousBook TOCNext »