Skip to content

Lab 04

In this lab you'll be revising the extraction of class hierarchies from informal descriptions, and they associated java syntax.

Carnivores and Herbivores

Carefully read through the following requirement description:

  • Contractually, all animals are required to be able to consume water and food. All animals must have:
    • a method "drink" (common to all animals, prints "Glug, glug, glug");
    • a method "eat" (implementation varies)
  • Animals fall into two sub-categories. They can be either herbivores or carnivores (we assume they can never be both).
    • Herbivores and carnivores share a common implementation for drinking.
    • Herbivores and carnivores do not share a common implementation for eating.
      • Herbivores print "Oooh yummy salad!"
      • Carnivores print "Roar, juicy steak!"
  • Only specific animal types must be instantiable (with new). These are:
    • Lion (Carnivore)
    • Tiger (Carnivore)
    • Zebra (Herbivore)
    • Gazelle (Herbivore)

Instructions

Your goal is to translate the above requirements into a hierarchy of java classes. There must not be any code duplication, i.e. methods copy-pasted from one method to the other. List all java files (classes or interfaces) you will need for the above implementation.

Create a new file inheritance.txt

For each class-file indicate whether it is:

  1. What kind of class it is:
    • A standard class
    • An abstract class
    • An interface
  2. What methods must be specified in the class
  3. Whether the class inherits from other classes

  4. If yes which other class, and which keyword is used.

Concept extraction

  • Translate the following technical requirements into conceptual java code, based on the concepts seen in class
  • The goal is to avoid any code replication (copy-pasted method code across classes).
  • Use the UML notation seen in class:

Recap requirements first

For each class-file decide on:
1. What kind of class it is: standard/abstract/interface
2. What methods must be specified in the class
3. Whether the class inherits from elswhere: keyword

Implementing an interface

Notation sample:

classDiagram
    class Interface {
        +foo()
    }

    class Class {
        +foo()
    }

    Interface <|.. Class : implements

Extending a class

Notation sample:

classDiagram
class BaseClass {
+bar()
}

    class ChildClass {
        +foo()
    }

    BaseClass <|-- ChildClass : extends

Implementation

After you have elicited a fitting class hierarchy on paper, translate your conceptual solution into an implementation.

  • Create the required hierarchy of Java classes.
  • Ensure there is no code duplication between methods.

Code Revision

In this exercise you're confronted with a short piece of software that "works", but showcases a bad programming style that results in confusing outputs in certain situations.
Your job is to:

  • Identify the issue.
    • Inspect the code for at least 5–10 minutes.
    • Play around and execute the code until you fully understand it and what inputs cause confusing output.
    • Do not ask an AI.
  • Correct the issue.

Code

There are only three classes. I recommend starting a new project in IntelliJ and pasting the code into the corresponding classes.

Launcher

package handling;

public class ExceptionHandlerLauncher {

  public static void main(String[] args) {

    // The closestRoot algorithm has an exception in its signature, so we need a try/catch block.
    try {
      System.out.println(
          "The closest int to your square-route is: " + ClosestRoot.getClosestRoot());
    } catch (Exception e) {
      System.out.println("This is not a parseable number!");
    }
  }
}

Custom Exception

package handling;

public class BlameTheUserException extends Exception {

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

ClosestRoot

package handling;

import java.math.BigInteger;
import java.util.Scanner;

/**
 * This class computes the "closest" int to a provided number's squareroute, provided on command
 * line. It throws an exception, if the provided input cannot be parsed.
 * Since user inputs are potentially very large, we internally use the BigInteger class for
 * computing the squareroute.
 *
 * @author Maximilian Schiedermeier.
 */
public class ClosestRoot {

  /**
   * Reads a value from command line and then returns the closest int to the input's squareroute.
   *
   * @return closest int to the inputs square root.
   * @throws BlameTheUserException if the provided input cannot be parsed to an int.
   */
  public static int getClosestRoot() throws BlameTheUserException {

    // Tell the user what to do. Then read the input.
    System.out.println(
        "Hey user, give me a number, and I'll compute the closest int to it's square route for "
            + "you!");

    // Now actually read what comes back.
    Scanner sc = new Scanner(System.in);
    String userInput = sc.next();
    BigInteger userInputAsBigInteger;

    // Next we need to convert the input to an int (ignore the fact that we could have used
    // nextInt()). This may fail, because the user may have provided something else than a
    // number, e.g. "Hello!". If it fails, we want to be specific and blame the user with a
    // custom exception.
    try {
      userInputAsBigInteger = BigInteger.valueOf(Integer.parseInt(userInput));
    } catch (NumberFormatException nfe) {
      // Our custom exception is **not** a RunTimeException, thus it has to be added to the
      // method signature.
      throw new BlameTheUserException("The input provided by the user is not a parse-able number.");
    }

    // If we were able to parse the input, we can proceed.
    return userInputAsBigInteger.sqrt().intValue();
  }
}

What is the issue

Do not access the solution until you have made a reasonable effort to find the answer yourself. Write your answer on a sheet of paper, as if it were a graded exam question.

What is the issue

The program illustrates the bad programming style of catching the Exception base class. This means the catch block is activated for any exception, even exceptions that have nothing to do with our custom exception thrown. The issue arises when negative numbers are passed: the error message suggests the user is to blame, but in reality the code never anticipated this possibility. A clear programmer's negligence is concealed behind a generic catch Exception e.

What to do about it

Do not access the solution until you have made a reasonable effort to find the answer yourself. Correct the program and justify your changes as comments, as if it were a graded exam situation.

What to do about it

There are multiple options, but the first change should, in any case, be to modify the catch block so that it only catches the exception declared in the called method's signature. The rule of thumb is to never catch a RuntimeException, and never list a RuntimeException in a method signature. Secondly, the code should be hardened against the possibility of receiving negative inputs. There should be an explicit check in the method, throwing a BlameTheUserException with a tailored error message. Alternatively, the code could convert the input to a positive integer using the Math.abs method. In any case, the code should document how it deals with negative inputs and provide this information as JavaDoc to the developer relying on this piece of code.