Learn Programming in Java

[Preliminary, this needs work]

<<Previous | ToC | Next >>
 

Lesson #6: Classes & Objects

Java is called an object-oriented programming (OOP) language because the main feature its promoters (myself excepted) like to brag about is the encapsulation of code into classes, which are instantiated into objects, and the only way to run your code is by reference to those objects. That's helpful when you have large teams of anarchistic programmers (and all of us are ;-) working on the same project and you are trying to manage its orderly development. For small or single-person projects, OOPS mostly just gets in the way and slows you down. That is a good thing, because computer bugs happen most often from failing to think through the problem domain, so thinking slower is much better. Anyway, all the small programs have already been written, so if you get into this profession, you must work on teams, and the encapsulation feature of OOPS helps to minimize (but not completely eliminate, as we learned in our AVP efforts) the friction between team members. But it only works if the team members actually do their job, which often didn't happen in AVP. Good management should prevent that, but good management is not my strong point.

Another claimed benefit of OOPS is code re-use, but as you already know, you can do that without even knowing about OOPS, and often OOPS programs are bigger and slower than non-OOPS programs that do the same thing. The good that OOPS does for you is slow you down (because many problems you want to program do not fit naturally into an OOPS structure, requiring extra thinking to figure out how to do it that way) so that you think more clearly about what you are trying to do, and that better thinking does help you to notice opportunities for code re-use that you should have seen anyway. You will actually make faster progress (fewer bugs, so less time debugging) if you spend more time up-front thinking about what you are doing. Embrace the slow-down, it is your friend.

A much more important benefit of OOPS is the encapsulation, which is related to strong data types. In the Goode Olde Dayes programmers put their data anywhere they felt like, then when they wanted to change the data, they did it anywhere they felt like, and the program was a scrambled mess. Small programs (like Tic-Tac-Toe) you can do that with impunity, but when they get bigger, if you try to do that, you have different parts of the program stepping all over each other, like last year's AVP. So we put the data inside a class, and we put all the program parts (subroutines, but called "methods" for no particfular reason) inside the same class, and the methods control access to the data so it doesn't get trampled. Again, OOPS only helps you if you want to do The Right Stuff, it cannot prevent cowboys from making a mess.

Some -- perhaps many -- computer people, certainly most of the educators, believe you cannot write good programs without OOPS, but as you already saw, that's not true. OOPS is very important, mostly as a way of organizing large programs and protecting you from stupid mistakes that are easy to make in non-OOPS languages, but it's not the essence of programming. The Six Things you learned in Breakfast, and then relearned in Chomp -- well, actually, the first Five of them (not counting subroutines) -- are the essential components of software. Those Five comprise a Universal Turing Machine as explained in "Why Six". Subroutines (and objects, which are the same thing writ large) are an important convenience, but you can and did write useful programs (like Tic-Tac-Toe) without them. It's just harder.

Anyway, I originally planned a large part of the back half of this course to be spent learning how to do things in OOPS. Today I want to just open the door a crack so you can figure it out on your own using public resources (including this website).

When you opened the StartHere program in BlueJ, I encouraged you to ignore everything except the one line marked "REPLACE THIS LINE". Now we will look outside that space at the rest of the program. Everything in Java is in classes, and the StartHere program has two classes, one of them named "Hello" where we did all our programming, and the other named "Zystem" which I sort of left as a black box. We also used a built-in class "System" for output to the console window and (indirectly, through Zystem) input from it. There are a zillion Java library classes that do all kinds of interesting and useful stuff. When you read through other people's Java code -- which I strongly encourage you to do -- you will see that they "import" numerous classes to use the methods defined within those classes.

Most of the time, the methods associated with a particular class work on private data defined in that class. The elements of that private data are called instance variables and they look just like the local variables we used in our own code -- that's because classes look just like subroutines -- except they have data and more methods defined outside the methods and visible to all the methods in the class (but mostly not outside the class, except as the programmer chooses). For example, you might want to display something in a window on the screen, so you'd use a class named something like Window to do the work for you. Inside the class, not visible to your code, are all the things a window needs to manage in order to be seen on the screen, such as its size and location, and what else is visible on it, plus the window frame and title (if any), and so on. There are methods to access that information in a well-defined way, so you can't crash the Java run-time environment or (worse) the whole computer system. All that work has been carefully thought out, and the resulting classes are in the Java library for you to use.

When you define your own class in Java, you get to specify the data elements -- which should all be "private" -- and the methods to operate on those elements. Some methods are "public", that is, they are visible to (and can be called by) users outside of your class; other methods -- if the users cannot do useful things with them -- should be private. When we did the two subroutines for the TTT game, they probably should have been private, but then I would have needed to explain that before its time.

I mentioned that some kinds of data do not fit into the class structure at all -- the built-in library class Math is one of them -- and others are clumsy at best as objects -- the built-in library class String is an example. Methods that do not make sense applied to objects (like the magical method "main" and the two TTT subroutines we did in the previous lesson) can be qualified as "static" and then they work as if they are ordinary subroutines unrelated to the class structure -- except that you need to use the class name to call them from outside the class. Class String and others that have been force-fit into a class structure where it makes little or no sense, well, you just need to work around the goofy APIs (for "Application Programming Interfaces," which is what we now call what used to be simply "subroutine libraries"). There are worse things in the world, for example trying to live in the region that used to be Iraq, or not having computers at all. A professional programmer works with the tools available, and despite all its remaining flaws, Java is a far better language to be working in than C.

OOPS helps you to think about your data and what kinds of things should be done to it -- or rather, OOPS makes it harder to not think about those important topics. It's still possible to evade that responsibility, but you should resist the temptation. This is more or less how you go about that important task...

There are two parts to this analysis: what is the data, and what your program needs to do to the data. If the data has many numbers or other parts to a single entity, then you call that entity an object (defined by a class) and define those numbers and/or other components to be instance variables in that class. For example, we might define a "class Person" in a database for an organization like a school or office workplace, with all the things that we need to know about that person, like name and birthdate and what they do and maybe a picture -- which itself is an object with height and width and perhaps a list of drawing commands, or  else the raw pixels (for an example of a Picture class, see Cay Horstmann's "Simple Java Graphics"). The methods of our class are all the things you might want to do with this object (Person), such as change values if they are found to be in error or the person changes job or the student graduated to the next grade, stuff like that, perhaps like this:

public class Person {
  private String name;
  private int birthDate;
  private String whatTheyDo;
  private Picture seeEm;

  public void setName(String nuname) {
    name = nuname;
  } end of setName

  /// other accessor methods TBD

  public Person() {} // constructor (use default)
} // end of class Person


Traditionally Java class names are capitalized, while variables and method names start with lower case (but if a name should be read as two more English words, the additional words may be capitaliized or set off with underscores. After the class is defined, Person can be used as a data type just like int or String or boolean. So let's say you declare a variable mary of type Person, and you initialize it, either by getting her out of the database, or else by creating a new object, thus:

Person mary = new Person();
The first "Person" in this line declares mary to be a reference to an object of type Person. The keyword "new" tells the compiler that you want the system to allocate some memory to store this object in, and the "Person()" which looks like a method call (because it is!) calls the constructor method for this class to do whatever is needed to initialize this instance object of the class.

Now if the class describes students in school, one of the methods might be to "public void graduate(int tograde)" so when it's time for Mary to graduate to 8th grade, your program would call "mary.graduate(8);" mary is a variable known to be type Person, and graduate is a method of class Person, so the compiler knows how to do what you just told it. Remember, mary is only the name of a variable, not the actual living breathing person (Mary) you know and talk to. It is the name of a place in memory that is initially an undefined value. If you called a method mary.setName("Bill"); Java would have no trouble putting all wrong values in there. But at least it would refuse to let you mary.setName(3.14); because 3.14 is the wrong data type. Strong types give you that protection. The computer does exactly what you tell it to do, even if you didn't want to tell it to do that, but only if you told it that is permitted. As we get farther into this, you will get some experience setting up and using your own classes.

Suppose you aren't ready to create a new object of type Person. Maybe you want to read objects off the database looking for every person whose birthday is tomorrow, and what if you didn't find any? There is a magical value that any object reference variable can have, sort of like zero, but it's spelled "null". If the variable mary is null, then there is no object to find the setName method in, it is not the reference to any object in memory, which is an error if you try to set its name. Do that and your program will crash (fail) -- except there's a way to recover from those kinds of runtime errors that we will cover in the next lesson. Generally you should protect yourself against that kind of error:

if (mary != null) mary.setName("Bill");


It is the nature of the OOPS way of doing things that you need classes for everything (usually except numbers). So the database you have all these students in is itself a class, and the school is a separate class. Some of these will be defined as APIs in the class library, others you need to invent yourself. This is the hard part, but you can always go back and add more classes and more instance variables and more methods. God got His creation perfect in six days, the rest of us need to work at it and rework it and keep on working it when (not if) it doesn't behave properly. Hmm, I guess God did that too.

It's the nature of creativity, and the modern industry word for it is "agile," where you build a minimal version of what you have in mind, then get feedback from users, then improve it. It's not really Darwinian, because you are purposefully designing both the initial idea (which didn't evolve out of nothing), and also each incremental iteration. That's an important difference between the American approach to engineering -- the idea springs fully-formed (or at least half-baked) from the mind of the designer -- and the Japanese model, where they take an existing product that works and incrementally make it better. Anybody can improve on things, but the Japanese do it very well; on the other hand, you are in the US of A, we create new ideas that didn't exist before. We are the leaders, they are the followers. The Japanese do agile and do it well; we pretend to do agile, while actually inventing new stuff that didn't increment from anything. Don't tell the agile folks I said that, they prefer to believe that they didn't invent anything, it just evolved, but they're wrong, they're Americans, and they (the successful ones) did invent new stuff, then improved it incrementally. "Go thou and do likewise." But that's next week.
 

The Mechanics of OOPS

When we worked in Chomp, everything was a number. In Tic-Tac-Toe some data were character Strings. With OOPS you can put strings and numbers and other things -- and the program code to work on those things -- all together and wrap a class around it and give it a name like as if it was just another number. It's like subroutines in spades.

And you can create new classes that are "the same only different" by inheriting the parts of the other class you want to keep, and overriding the parts you want different. Take for example, the  class Person we mentioned above. Suppose we want a special kind of person, a Parent, who is still a person, but also has a spouse and children. These are other Persons, but we don't have a place in the Person class to mention who the spouse and children are. So we define a new

public class Parent extends Person {
  Person spouse;
  Person firstborn;

  public void setName(String nuname) {
    ...
    if (OK) super.setName(nuname);}
  }

Then we can override the setName method so that we must correlate the name of a Parent to the name of their spouse, and if it's OK, then let the Person version of setName (super gets the immediate superior in the class hierarchy) do its thing. Let's see how this works. Recall that mary is a Person. Now we also create another Person, but let's make bill a Parent (Parents are still also Persons)
Person bill = new Parent();
So if I ask to set the name of bill, the OOPS system will see that under the hood bill is really a Parent, so it will go through the extra code to see if his spouse's name is suitable. OOPS does that.

I was going to work up some examples to give you some experience with objects, but then I decided to just throw you into the deep end of the pool. Don't worry, you won't drown. So ignore the (incomplete) rest of this page and jump right into GameEngine, which is very OOPSy. It will be fun!
 

For the rest of today we will be using existing library classes and not a little magic* to do something interesting.

[I gotta rework Chomp as a Java example here, probly intro it earlier up-page]
[The original idea was to have them build a window with some widgets, so clicks do something, but preliminary research suggests that this may be too steep for this stage in their learning... Better: Chomp already does a window with widgets, so they get to look at it and make small changes]
 

Next: Extras

<<Previous | ToC | Next >>

Revised: 2020 July 9


* Magic is a technical term, it means "Happens by means unspecified."