Reading

Required: These notes!
Recommended: Java in a Nutshell, Chapt. 3, section "Fields and Methods".

Object Oriented Programming Lesson One

With this lesson we start learning Object Oriented Programming (OOP). Object Oriented Programming as a paradigm has four (or three, depending on how you count) fundamental tenants: encapsulation, data-hiding, inheritance, and polymorphism. Today we kick things off with encapsulation.

The mechansim: how instance methods (a.k.a. non-static member functions) work

So far we've seen classes used in two ways: collections of functions and collections of data. Of course, we could mix the member data and the static member functions we've been dealing with into a single class, but that wouldn't get us quite to the "objects" of Object Oriented Programming. To get there, we need to understand a new mechanism: non-static member functions, which in Java parlance are called instance methods (as opposed to the static member functions which are called static methods in Java parlance).

First let's recall how instance data works. Imagine you have a class Point defined as follows:

public class Point
{
  public double x, y;
}
... and suppose in some other class file you create two instances like this:
Point A = new Point();
Point B = new Point();
A key observation is that at this point, the expression x or the expression y or an expression like Math.sqrt(x*x + y*y) makes no sense. Why? because nobody knows which x or y you mean! x's and y's belong to instances of Points. So you need to say something like "the x that belongs to the Point that A refers to" (which we write as A.x), or "the x that belongs to the Point that B refers to" (which we write as B.x).

A function defined without the modifier static works the same way. If we add to the class Point like this

public class Point
{
  public double x, y;
  public double distance()
  {
    Math.sqrt(x*x + y*y);
  }
}
and imagine the same two instances
Point A = new Point();
Point B = new Point();
... the expression distance() makes no sense. Why? Because nobody knows whether you mean the distance() that belongs to the Point A refers to or the distance() that belongs to the Point B refers to. We think of instance methods belonging to instances just like instance data, and we specify which instance is inteded in exactly the same way: A.distance() versus B.distance(). Looking inside the definition of distance, you see the expression Math.sqrt(x*x + y*y), and you might be tempted to think that it violates what I said earlier, i.e. which x?, which y?. The answer is the x and y that belong to the same instance of Point that this distance() belongs to. In other words, when you make the call
A.distance()
... the x and y in Math.sqrt(x*x + y*y) are the ones belonging to A. When you make the call
B.distance()
... the x and y in Math.sqrt(x*x + y*y) are the ones belonging to B.

Underneath the hood with instance methods

Conceptually, each instance of class Point in the above example has its own distance() function, just like it has its own x and its own y. In reality, the way Java handles instance methods works a bit differently. In the implementation, the call A.distance() really acts like a call to distance(A), and this is always the case.
A call to instance method foo of the form obj.foo(arg1,arg2,...,argk) ... is actually a call to a static function foo(obj,arg1,arg2,...,argk)
In fact, this is so literally true, that this implicit parameter that is the object on which the method was called (i.e. what's before the dot), which has the name this, can be used inside the function definition. For example:
public void addToMe(Point B) { x += B.x; y += B.y; } same as public void addToMe(Point B) { this.x += B.x; this.y += B.y; }

So, to clarify, when we make a call to a non-static member function (instance method in Java parlance), the object we call with, i.e. the object before the ".", is implicitly an extra parameter named "this". Hopefully the illustration below clarifies things.

Point.javacall stack during call a.addToMe(b)

The Object Oriented Paradigm: Part 1

Following the Object Oriented Programming paradigm, a program consists of object instances communicating by calling each others' methods. Thus instead of the function being the fundamental unit of a program, as it is in procedural programming, the object instance is the fundamental unit of a program. Each instance of a class has a well-defined interface — the collection of its method prototypes (i.e. the first line of the method declaration) and, hopefully, a bit of documentation — and a well-defined implementation — the definitions of its methods and its data members (more properly called fields is Java parlance). So what's the difference? Well, object instances have data-members/fields, so they have memory or, as computer scientists more formally would say, they have state. The upshot of that is that calls to the same method for the same instance with the same arguments can give different results over time, because the instance has memory/state that can evolve as the program executes. This matches the way things work in the real-world (if p is an instance of class Person, p.weight() give a different answer after a big dinner than it did before), and matches the way we like to think of many abstractions in software systems.

So, when sitting down to design a program in Java, instead of asking yourself "what functions will I need?", you ask yourself "what classes will I need?"; and answering that question will require you think about collections of methods you want to be able to call for each different type of "thing" in your program.

Object Oriented Design: A simple example

Let's consider a simple example. I'd like to write a program that keeps track of batting results in baseball, in order to report players' batting averages. For simplicity, we'll assume that the result of a player stepping up to the plate will either be a walk, a hit, or an out. The formula for batting average is hits/atbats, where an at-bat is an appearance that resulted in a hit or an out — i.e. walks are ignored. In my program, I'd like to be able to handle many players.

In considering an OOP approach, we would identify that a player should be an object in our program. We should have a method that allows us to record the results of one or more plate appearances, and we should have a method that reports the player's current batting average. This leads us to an interface like this:

class Player
{
  void record(String outcomes) ← records outcomes of plate appearances; outcomes is a string of h/w/o's like "hoowh"
  double average()             ← returns the current batting average, no rounding
}
If we had this kind of interface, we could write code like this:
Batter b = new Batter();
b.record("owhoowoowhhwoohoo");
System.out.println(b.average());
b.record("hhowowohhwohoohoowoooh");
System.out.println(b.average());
Of course, whole teams worth of batters would work the same way. We'd just have arrays or linked lists of Batter objects, each recording and reporting their own batting averages.

So what about implementing this? The implementation has to remember things in order to be able to report a batting average. This means it has to have data-members/fields. What we want to remember is an implementation decision. One option is to keep a count of at-bats and a count of hits. That leaves us with something like the following

Batter.javaTeam.java
public class Batter
{
  int hits;
  int atBats;

  public void record(String outcomes)
  {
    for(int i = 0; i < outcomes.length(); i++)
    {
      if (outcomes.charAt(i) == 'h') hits++;
      if (outcomes.charAt(i) != 'w') atBats++;
    }
  }

  public double average()
  {
    return (double)hits / atBats;
  }

  public static void main(String[] args)
  {
    Batter b = new Batter();
    b.record("owhoowoowhhwoohoo");
    System.out.println(b.average());
    b.record("hhowowohhwohoohoowoooh");
    System.out.println(b.average());
  }
}
public class Team
{
  public static void main(String[] args)
  {
    Batter A = new Batter();
    Batter B = new Batter();
    A.hits = 0;
    String[][] res = { {"oohw","ohoh"},{"howoo","woho"}};
    for(int i = 0; i < res.length; i++)
    {
      A.record(res[i][0]);
      B.record(res[i][1]);
      System.out.println("A: " + A.average()
			 + ", B: " + B.average());
    }
  }
}
Sample Run
~/$ java Team
A: 0.3333333333333333, B: 0.5
A: 0.2857142857142857, B: 0.42857142857142855