I recently posted some sample code that I was providing students with and got great feedback so figured I post another one of the examples I will be providing students with. (See Simple Example of an Iterable and an Iterator in Java) This one is a simple postfix calculator. The main learning objectives are basic inheritance, trees, post-order and in-order traversals and stack based parsing of postfix expressions.First, the CLI code:import java.util.Scanner;public class PostfixCLI {    public static void main(String[] args) {        System.out.println(Enter postfix expression to evaluate, 'exit' to exit.);        Scanner in = new Scanner(System.in);        while (true) {            try {                System.out.print(>>> );                String input = in.nextLine();                if (input.equals(exit)) {                    break;                }                Expression expression = Expression.parsePostOrder(input);                System.out.format(%s = %.4f\n,                                  expression.toString(),                                  expression.evaluate());            } catch (InvalidExpressionException e) {                System.out.println(Invalid expression:  + e.getMessage());                continue;            } catch (RuntimeException e) {                System.out.println(Runtime error:  + e.getMessage());                continue;            } catch (Exception e) {                System.out.println(Unknown error:  + e.getMessage());                continue;            }        }    }}And the Expression.java file that does most of the work.import java.util.Stack;import java.util.Scanner;public abstract class Expression {    public abstract double evaluate();    public String toPostOrder() {        return this.toString();    }    public String toInOrder() {        return this.toString();    }    /*     * Parse an expresssion tree given a string and return the resulting tree.     * InvalidExpressionException is raised in cases of invalid input.     */    public static Expression parsePostOrder(String expression)            throws InvalidExpressionException {        Scanner tokenizer = new Scanner(expression);        Stack<Expression> stack = new Stack<Expression>();        while (tokenizer.hasNext()) {            String token = tokenizer.next();            try {                double number = Double.parseDouble(token);                stack.push(new Number(number));            } catch (NumberFormatException e) {                // If control reaches here it's because the token is not a                // number, so it must be an operator.                if (stack.size() < 2) {                    throw new InvalidExpressionException(                            Not enough parameters for  + token);                }                Expression right = stack.pop();                Expression left = stack.pop();                stack.push(BinaryOperator.fromSymbol(token, left, right));            }        }        if (stack.size() != 1) {            throw new InvalidExpressionException(Not enough operators.);        }        // The single item left on the stack is the root of the tree.        return stack.pop();    }}class Number extends Expression {    double number;    public Number(double num) {        super();        this.number = num;    }    @Override    public double evaluate() {        return this.number;    }    @Override    public String toString() {        return Double.toString(this.number);    }}abstract class BinaryOperator extends Expression {    Expression left;    Expression right;    protected abstract String getOperatorSymbol();    public BinaryOperator(Expression left, Expression right) {        super();        this.left = left;        this.right = right;    }    @Override    public String toString() {        return this.toInOrder();    }    @Override    public String toInOrder() {        return ( + this.left.toInOrder() +   + this.getOperatorSymbol()                +   + this.right.toInOrder() + );    }    @Override    public String toPostOrder() {        return this.left.toPostOrder() +   + this.right.toPostOrder() +                  + this.getOperatorSymbol();    }    public static BinaryOperator fromSymbol(String symbol, Expression left,            Expression right) throws InvalidExpressionException {        if (symbol.equals(+)) {            return new AddOperator(left, right);        } else if (symbol.equals(-)) {            return new SubtractOperator(left, right);        } else if (symbol.equals(*)) {            return new MultiplyOperator(left, right);        } else if (symbol.equals(/)) {            return new DivideOperator(left, right);        } else {            throw new InvalidExpressionException(Invalid operator:  + symbol);        }    }}final class AddOperator extends BinaryOperator {    protected AddOperator(Expression left, Expression right) {        super(left, right);    }    @Override    protected String getOperatorSymbol() {        return +;    }    @Override    public double evaluate() {        return this.left.evaluate() + this.right.evaluate();    }}final class SubtractOperator extends BinaryOperator {    protected SubtractOperator(Expression left, Expression right) {        super(left, right);    }    @Override    protected String getOperatorSymbol() {        return -;    }    @Override    public double evaluate() {        return this.left.evaluate() - this.right.evaluate();    }}final class MultiplyOperator extends BinaryOperator {    protected MultiplyOperator(Expression left, Expression right) {        super(left, right);    }    @Override    protected String getOperatorSymbol() {        return *;    }    @Override    public double evaluate() throws RuntimeException {        return this.left.evaluate() * this.right.evaluate();    }}final class DivideOperator extends BinaryOperator {    protected DivideOperator(Expression left, Expression right) {        super(left, right);    }    @Override    protected String getOperatorSymbol() {        return /;    }    @Override    public double evaluate() {        double left = this.left.evaluate();        double right = this.right.evaluate();        if (right == 0) {            throw new RuntimeException(Division by zero in  + this.toString());        }        return left / right;    }}
Basic Postfix Calculator in Java
java;tree;inheritance
General Use full JavaDoc for method comments. Instead of Throw X in case Y go for @throws X if YYou never need to call the default (zero-argument) superclass constructor. If the first statement of a constructor isn't a call to some superclass constructor, a call to the default constructor is inserted if there is one. If not, the class won't compile. (Thanks @cHao!)You can drop this. when accessing other members unless there's ambiguity. If you find yourself needing to use it outside constructors and setters--which contain lines like this.field = field--take it as a subtle hint that you may need to refactor the method or rename a local variable.The protected access level on a constructor for a final class is equivalent to package-private; drop protected to be explicit. That being said, if this is a beginning class you may want to stick with public classes until you can cover the reasons for using package-private.Update: I expand a bit on some of the original items and address some more higher-level design issues below.PostfixCLIThis class is very simple, but there are a couple small changes you can make to start teaching good habits to your students right away.Keep main to a few lines, enough to instantiate the REPL class and run it. This makes reusing it and writing unit tests easier and limits main to bridging the gap between the JVM launching your application with arguments and the application itself.As @Vogel612 states, you should rarely catch the top-level exceptions (RuntimeException, Exception, Throwable, et al). Especially when learning it's better to let them propagate to the JVM and kill the process.Since you've created your own exception hierarchy, the only exceptions getting through should be utterly unanticipated: OutOfMemoryError, StackOverflowException and the like. There's nothing you can do about these here, and they will indicate a serious problem in the code that requires attention.Granted, in a customer-facing application you'd end up catching these in a generic fashion, logging them for a debug session, and exiting the application as gracefully as possible. Rarely will you be able to do anything more than let the exception bubble up, and students won't be at the level of experience to do anything better for a long while.I'm pushing hard on this one because every project on which I've worked has required going through pages of code to remove catch (Exception e) { e.printStackTrace(); } as it makes tracking down errors very difficult.Expression (minus the parser)Expression.toInOrder calls toString while BinaryExpression.toString calls toInOrder. This works, but it's very confusing. Is there a better way? (Maybe not)Should evaluate return a Number instead of a raw double?NumberTo go with evaluate and differentiate it from Number, consider renaming the number field to value. The constructor argument can also be value since there's no reason to shorten it to val or num.BinaryOperatorAdd a field to store the operator in BinaryOperator and pass it from each subclass constructor, removing the need for getOperatorSymbol().Instead of throwing a generic RuntimeException when dividing by zero, create a custom DivideByZeroException or better still, handle NaN inside Number and the other operators.It would be great to separate the operator itself from its use in an expression with left and right values. However, I understand you must keep it simple enough for your students, so I'll leave it at that. :)ParserIf the user enters 2.42.74, it will be erroneously reported as not having enough operands. Instead, parse the token to an operator, and only when that passes do you know the real problem.Once you change the above, you can handle unary operators like 1/x. The operator should know how many operands it needs, and it should be given the stack itself rather than pulling off two values in the loop.The parser is doing way too much to cram it into a single method, and that it knows every operator requires two operands paints yourself into a corner. The first step is to move this code into a separate class that can maintain the stack and do the work to manipulate it across methods.The parsing method needs to be refactored into methods to scan the input, parse each token, apply it to the stack, and finally return the result. Here's a rough cut:public class Parser {    private final Stack<Expression> stack = new Stack<>();    public Expression parse(String input) {        Scanner tokens = new Scanner(expression);        while (tokens.hasNext()) {            parse(tokens.next());        }        if (stack.size() != 1) {            throw new InvalidExpressionException(Not enough operators);        }        return stack.pop();    }    private void parse(String token) {        ... parse Number or Operator and place result on stack ...    }}