Programming languages exist to solve problems. Because the problems one might want to solve are so varied, and because computers are made to be general purpose (to solve problems in a wide variety of areas), it should come as no surprise that different programming languages take very different approaches to code organization and coding techniques.
For the simplest type of computer programs, organization of a large body of code isn't as important. Therefore, small or simple programs often use a method of organization called imperative or procedural programming. The steps in such a program are written in order, one after the other. It's easy to write programs this way if you already know the tasks you want the computer to perform, and it's also easy to understand a program written by someone else, since you can simply read each step.
Procedural programming becomes more difficult as you write larger or more complex programs. You find that you're writing the steps over and over, making your own task of programming seem repetitive and mechanical, so perhaps you create functions to execute sets of tasks in order. But you're still left with one problem: what about the data used by those functions, such as the values of variables? Now you need to somehow keep track of what data to pass to functions. There has to be a better way. Later, you discover that as you write your program, some of the tasks you'd like the computer to do are similar in nature, but not exactly the same. For now, you make copies of your functions and edit them slightly to reflect the differences, but as your project grows, you find that you're editing the functions more and more, trying to keep the common parts of the code in sync. It rapidly becomes a hassle.
Isn't there some way to tell the computer, "Do almost what you did before, but make these slight changes?" Isn't there a way to somehow associate the data used by a part of your code with the code you wrote specifically to act upon that kind of data? Object-oriented programming is an attempt to solve these problems, and more.
So far, we've identified two cases where imperative and procedural programming just doesn't scale up to larger, more complex programs. What would make our task easier?
Computer programs are often modeled on items in our real, physical world. Perhaps your program actually controls some machine, with all its lights and levers and gears, or perhaps your program is controlled by something or someone in the real world. Perhaps your program does calculations with data collected from the outside, data that represents the world around it. Or perhaps, as you program, you find that solving problems is easier when you represent data and code as a thing with its own nature, separate from other things.
Real-world things are objects without abstraction; they really exist. Just as objects in computer programs can be explained by analogy to physical things (we'll be doing this often), we can do the reverse, too.
An automobile is a physical object. You've probably purchased yours based on the things it can do. For example, if you need to carry a lot of cargo, you might purchase a pickup truck. If you live in a climate where you need extra grip on rocky or icy roads, you might purchase a model with all-wheel drive. If you spend lots of time on the track, you might purchase one that has a lot of power, or is light, or can make rapid turns. Cars overall have a lot in common. They usually have four wheels, they usually have an internal combustion engine, there is usually a driver's seat with a steering wheel and pedals for controls, and so on. There are very many models of cars, and while some differ slightly and some differ greatly, they're all just a little bit different. In object-oriented programming, we call these features behaviors and they determine what your model of car can do. Note that other people who buy a similar model of car can do (mostly) the same things.
Because cars exist, and because they do things, they are in certain conditions or modes. For example, a car that's parked is in a very different state from one that's driving — the parking brake is off, the engine is running, the transmission is in gear, and perhaps the headlights are on if it's raining or nighttime. When parked, the parking brake is set, the engine is off, the transmission is set to "Park," and the parking brake is engaged.
Because your car may have many features, and the features are separate, they can be used separately, too. For example, you can run the engine when the car is parked. You can turn on the headlights when nothing else is in use. Each one of these features is in some condition, or state, but they're all associated with your particular car — after all, turning on your headlights doesn't turn on the headlights of all cars. The state of your car depends on how you've set the controls, and it only applies to your car. You can change the state of many things in your car as you operate it. You might turn on the heater or the windshield wipers. State as a whole reflects the condition of all the things in your car.
Imagine for a moment a program that represents a simple circuit with a light bulb, a battery, and a switch. Don't worry about what the code might look like, just think about how we might represent the idea of this real-world object. Well, what if we had a real physical circuit on the desk in front of us? What can we tell about its states and behavior?
The point of the circuit is to make light from electricity. We could do that just by attaching the light to the battery. But if we did that, we would soon use up all the energy in the battery. Our circuit has an extra "feature" that we can activate it and deactivate it at will.
This feature leads to two object-oriented ideas. One is that this circuit certainly has state. It's either on or off — it's never both, or neither. This means that, in our code, we need a single place to store the on-off state. We also need a way to change the state from off to on, and vice versa. These are the behaviors of our object. One is that we can turn it on (if it's already on, we can decide what to, such as notify the user, or just ignore the request. In the same way, we can turn it off if it's on.
Here's what the idea looks like:
You need to enable Javascript to see this example.
In Java, like many OOP languages, objects are defined in classes. A class is a sort of recipe. It isn't a cooked meal, it's just a description of how to make the meal. You can cook a meal following a recipe as many times as you like, and although they'll be different meals on different nights, they'll be virtually identical if you've followed the recipe exactly.
A class becomes an object when you instantiate it. That is, you tell the computer to follow the recipe and make the variables and code as described. Once instantiated, you can assign the object to a variable, call its functions, look at its data, and even throw it away when you're done using it. You can make as many as you like, and every one keeps its own state. In our example above, you could make two lights, turn one of them on and leave the other one off. So how do we define a class in Java? Let's take a look.
class Light {
boolean lit = false;
public void turn_on() {
lit = true;
System.out.println("Turned light on.");
}
public void turn_off() {
lit = false;
System.out.println("Turned light off.");
}
}
We've given our class a name ("Light") that we'll use when we wish to instantiate a Light object. This adds our code to the list of things that Java can make.
The variable "lit" will hold the object's state (whether the bulb is lit or not). There are many ways to store this kind of state. We can make a string called "state" that contains the values "on" or "off," or we could use an integer and set the value to 0 for off and 1 for on. In this case, we've used a boolean to represent the state, and chosen a good name for the variable so it's clear what we mean. (Imagine if the variable name were "unlit" — we'd have to invert the value, and that would be confusing. Good variable naming and a well-chosen type are important for code organization.) This variable satisfies our need to store the circuit's state.
We need some way to change the state of the object from the outside, so that the light may be turned on and off. We define two functions for this purpose — one to turn the light on, and one to turn it off.