Thinking With Types: Java and object-oriented programming (OOP)

In Java I rarely used generic data types, and rarely thought about types much at all. To demonstrate why, I’ll go back to one of my favorite examples, writing code for a pizza store.

Please note that my Java code my not be 100% correct. I haven’t put this in an IDE or tried to compile it. I’m just trying to quickly demonstrate the OOP approach.

To start modeling a point-of-sales application for a pizza store, I’ll first need my usual enumerations:

enum Topping {
    Cheese,
    Pepperoni,
    BlackOlives
}

enum CrustSize {
    Small,
    Medium,
    Large
}

enum CrustType {
    Regular,
    Thin,
    Thick
}

Given those, let’s create a Pizza class. But before doing that, let’s list the attributes and behaviors of an OOP pizza. The attributes are basically what I just listed with those enums:

  • Crust size
  • Crust type
  • Types of toppings

The behaviors correspond to those attributes:

  • Add topping
  • Remove topping
  • Set crust size
  • Set crust type

If I also kept price-related information, a pizza could also calculate its own price, but since the toppings and crust attributes don’t carry price information with them, that’s pretty much all of the behaviors of a Pizza.

Having gone through this exercise several times before, I know that I want all of the food-related items in a pizza store — pizza, breadsticks, soda, etc. — to extend a base Product class:

public interface Product {}

In the real world a Product will have attributes like cost and potentially a sales price associated with it, but for the purposes of this exercise I’ll just leave it as a marker interface like that.

Given that, and the attributes and behaviors of a pizza, my initial code for a Java/OOP Pizza class looks like this:

public class Pizza implements Product {

    private List<Topping> toppings = new ArrayList<>();
    private CrustSize crustSize;
    private CrustType crustType;

    public Pizza(CrustSize crustSize, CrustType crustType) {
        this.crustSize = crustSize;
        this.crustType = crustType;
    }

}

That code is followed by a series of getter and setter methods that have these type signatures:

public CrustSize getCrustSize()
public CrustType getCrustType()
public List<Topping> getToppings()

public void setCrustSize(CrustSize crustSize)
public void setCrustType(CrustType crustType)
public void setToppings(List<Topping> toppings)

The thing to notice here is that this is all very simple code. The getters don’t take any input parameters and they return basic data types, and the setters all return void. I wrote Java code like this for more than 12 years.

In fact, if I wrote more code, such as for an Order class, you’d see that the pattern is the same, all very simple code:

public class Order {

    private List<Product> lineItems = new ArrayList<>();
    public Order() {}

    public void addItem(Product p)
    public void removeItem(Product p)
    public List<Product> getItems()

    public String getPrintableReceipt()
    public BigDecimal getTotalPrice()

}

The closest I get to using generic types in that code is this:

private List<Topping> toppings = new ArrayList<>();
private List<Product> lineItems = new ArrayList<>();

Summary, Part 1

To summarize what you saw in these examples:

  • My Java/OOP “pizza” code was very simple, with no generic types
  • A lot of methods return void
  • A lot of methods have no input parameters
  • I always use mutable data structures

Summary, Part 2

There are a few other things to say about that code, and bear in mind, I’m only talking about my own code.

(1) I didn’t realize it when I was writing Java/OOP code, but all of those methods that return void and take no input parameters tend to warp your brain. You can’t tell what the methods do by looking at their type signatures, so you either (a) trust that the methods are well-named, or (b) you have to look at their source code to see what they do.

As an example of what I mean, I trusted my own code because I knew that all of my getters and setters were just boilerplate code. But one time when I was debugging a problem with a junior developer, I found that he wrote a setter method like this:

public void setFoo(Foo newFoo) {
    openTheGarageDoor();
    walkTheDog();
    buyGroceries();
    solveWorldPeace();
    this.foo = newFoo + 1;
}

It wasn’t quite that exhaustive, but there was a lot going on there. And bear in mind, all of that happened inside a method with this type signature:

public void setFoo(Foo newFoo)

So, lessons learned:

  • This developer needed some more training
  • I couldn’t trust method type signatures, I had to look at their code

(2) A second point I’ll add is that many Java methods can throw exceptions, and when they can, you have to think about (a) handling the successful case, and (b) handling the failure case, such as wrapping the method call with try/catch. This gets to be very hard on the brain, so I got to the point that — using the Model/View/Controller paradigm — I only handled exceptions with my top-level controllers. I ignored exceptions at the low levels, and just let them bubble up to my controllers and handled them there.

I didn’t know anything about functional programming, and I couldn’t think of a better way to deal with exceptions, so that’s how I dealt with that problem.

Is Java/OOP code that simple? A statistical look

I haven’t worked with Java in quite a while now, so I wondered, “Was my code really that simple?” To check what I was thinking, I searched three old Java codebases — all projects that were in production in their day. I just did a simple search for the <.*> pattern, and found these stats in those three projects:

  1. 0.2%: 118 lines contain the <.*> pattern out of 74,200 lines
  2. 1.4%: 686 lines out of 49,189
  3. 1.2%: 313 out of 27,083

So out of about 150,000 lines of code there are 1,117 lines with a <.*> pattern, for a total of 0.74%. (Note that those line-count numbers include blanks lines and comment lines, so the actual percentages are higher.)

I then looked through all of those <.*> lines of output, and found that these were the two most difficult lines to read:

SortedMap<String, Integer> wordCountMap = new TreeMap();
public Class<?> getColumnClass(int columnIndex) {

So that’s a look at some Java/OOP code. Next, let’s look at some Scala code.