
Chapter 6. OOP – Using Other People's Hard Work
OOP stands for object-oriented programming. In this chapter, you don't need to even try and remember everything. Why do I say this? Surely, that's what learning is. The more important thing is to grasp the concepts and begin to understand the why of OOP rather than memorize rules, syntax, and jargon.
The more important thing is to actually start to use some of the concepts, even though you might have to keep referring back and your code might not properly adhere to every OOP principal that we discuss. Neither does the code in this book. The code in this chapter is here to help you explore and grasp the concepts of OOP.
If you try to memorize this chapter, you will have to make a lot of room in your brain, and you will probably forget something really important in its place such as going to work or thanking the author for telling you not to try and memorize this stuff.
A good goal will be to try and almost get it. Then we will start to recognize examples of OOP in action so that our understanding becomes more rounded. You can then often refer back to this chapter for a refresher.
So what is all this OOP stuff we will learn about? Actually, we have already learned loads about OOP. Until now, we have been using classes such as Button
, Random
, and Activity
, overriding methods of classes (mainly onCreate
) and using an interface as well; remember implementing onClickListener
a few times in the first five chapters?
This chapter just helps to make sense of OOP and expands our understanding, and finally, we will make our own classes.
Then we will be in a good position in the next two chapters to make two cool retro arcade games using lots of other people's hard work. This chapter will be mainly theory, but with a few practical console examples using LogCat so that we can see OOP in action.
In this chapter, we will do the following:
- Look at what OOP is.
- Write our first class.
- Look at what encapsulation is and how we achieve it as well as look more deeply at variables and the different types. We will also take a short break to throw out the garbage.
- Learn about inheritance and how we can extend and even improve upon a class before we use it.
- Take a look at polymorphism, which is a way of being more than one thing at a time and is really useful in programming.
What is OOP?
OOP is a way of programming that involves breaking our requirements down into chunks that are more manageable than the whole.
Each chunk is self-contained yet potentially reusable by other programs while working together as a whole with the other chunks.
These chunks are what we have been referring to as objects. When we plan an object, we do so with a class. A class can be thought of as the blueprint of an object.
We implement an object of a class. This is called an instance of a class. Think about a house blueprint. You can't live in it, but you can build a house from it, which means you build an instance of it. However, OOP is more than this. It is also a methodology that defines best practices such as the following:
- Encapsulation: This means keeping the internal workings of your code safe from interference from the programs that use it, and allowing only the variables and methods you choose to be accessed. This means your code can always be updated, extended, or improved without affecting the programs that use it, as long as the exposed parts are still accessed in the same way.
- Inheritance: Just like it sounds, inheritance means we can harness all the features and benefits of other people's hard work, including encapsulation and polymorphism, while refining their code specifically for our situation. Actually, we have done this already every time we used the
extends
keyword. - Polymorphism: This allows us to write code that is less dependent on the types we are trying to manipulate, making our code clearer and more efficient. Some examples later in the chapter will make this clear.
Tip
When we talk about using other people's hard work, we are not talking about a magical way to abuse copyright and get away with it. Some code is plain and simple, someone else's property. What we are taking about is the vast array of free-to-use code, particularly in the context of this book, in the Java and Android APIs. If you want some code that does a certain thing, it has probably been done before. We just have to find it, then use it or modify it.
Java was designed from the start with all of this in mind, so we are fairly significantly constrained to using OOP. However, this is a good thing because we learn how to use the best practices.
Why do it like this?
When written properly, all this OOP allows you to add new features without worrying as much about how they interact with existing features. When you do have to change a class, its self-contained nature means less, or perhaps zero, consequences for other parts of the program. This is the encapsulation part.
You can use other people's code without knowing or perhaps even caring how it works. Think about the Android lifecycle, buttons, threads, and so on. The Button
class is quite complicated, with nearly 50 methods—do we really want to write all that just for a button?
OOP allows you to write apps for highly complex situations without breaking a sweat. You can create multiple similar yet different versions of a class without starting the class from scratch using inheritance, and you can still use the methods intended for the original type of object with your new object because of polymorphism.
Makes sense, really! Let's write some classes and then make some objects out of them.
Our first class and first object
So what exactly is a class? A class is a bunch of code that can contain methods, variables, loops, and all other types of Java syntax. A class is part of a package and most packages will normally have multiple classes. Usually, but not always, each new class will be defined in its own .java
code file with the same name as the class.
Once we have written a class, we can use it to make as many objects from it as we need. Remember, the class is the blueprint, and we make objects based on the blueprint. The house isn't the blueprint just as the object isn't the class; it is an object made from the class.
Here is the code for a class. We call it a class implementation:
public class Soldier { int health; String soldierType; void shootEnemy(){ //bang bang } }
The preceding snippet of code is a class implementation for a class called Soldier
. There are two variables, an int
variable called health
and a string
variable called soldierType
.
There is also a method called shootEnemy
. The method has no parameters and a void return
type, but class methods can be of any shape or size that we discussed in Chapter 5, Gaming and Java Essentials.
When we declare variables in a class, they are known as fields. When the class is instantiated into a real object, the fields become variables of the object itself, so we call them instance variables. Whichever fancy name they are referred to by, they are just variables of the class. However, the difference between fields and variables declared in methods (called the local variables) becomes more important as we progress. We will look at all types of variables again in the Variables revisited section.
Remember, this is just a class, not an object. It is a blueprint for a soldier, not an actual soldier
object. This is how we make an object of the Soldier
type from our Soldier
class:
Soldier mySoldier = new Soldier();
In the first part of the code, Soldier mySoldier
declares a new reference type variable of type Soldier
, called mySoldier
, and in the last part of the code, new Soldier()
creates an actual Soldier
object. Of course, the assignment operator, =
, in the middle of the two parts assigns the result of the second part to that of the first. Just like regular variables, we could also have performed the preceding steps like this:
Soldier mySoldier; mySoldier = new Soldier();
This is how we would assign and use the variables:
mySoldier.health = 100; mySoldier.soldierType = "sniper"; //Notice that we use the object name mySoldier. //Not the class name Soldier. //We didn't do this: // Soldier.health = 100; ERROR!
In the preceding code snippet, the dot operator, .
, is used to access the variables of the class, and this is how we would call the method. Again, we use the object name and not the class name, followed by the dot operator:
mySoldier.shootEnemy();
Tip
As a rough guide, a class's methods are what it can do and its instance variables are what it knows about itself.
We can also go ahead by making another Soldier
object and accessing its methods and variables:
Soldier mySoldier2 = new Soldier(); mySoldier2.health = 150; mySoldier2.soldierType = "special forces"; mySoldier2.shootEnemy();
It is important to realize that mySoldier2
is a totally separate object with totally separate instance variables.
Also notice that everything is done on the object itself. We must create objects of classes in order to make them useful.
Note
As always, there are exceptions to this rule, but they are in the minority, and we will look at the exceptions later in the chapter. In fact, we have already seen an exception way back in Chapter 3, Speaking Java – Your First Game. Think of Toast
.
Let's explore basic classes a little more deeply.
Basic classes
What happens when we want an army of Soldier
objects? We will instantiate multiple objects. We will also demonstrate the use of the dot operator on variables and methods, and show that different objects have different instance variables.
You can get the working project for this example in the code download. It is in the chapter6
folder and is called simply BasicClasses
. Or read on to create your own working example:
- Create a project with a blank activity, just as we did in Chapter 2, Getting Started with Android. Clean up the code by deleting the unnecessary parts, but this isn't essential. Call the application
BasicClasses
. - Now we create a new class called
Soldier
. Right-click on thecom.packtpub.basicclasses
folder in the Project Explorer window. Click on New, then on Java Class. In the Name field, typeSoldier
and click on OK. The new class is created for us, with a code template ready to put our implementation within, just like what is shown in the following screenshot: - Notice that Android Studio has put the class in the same package as the rest of our app. Now we can write its implementation. Write the following class implementation code within the opening and closing curly braces of the
Soldier
class:public class Soldier { int health; String soldierType; void shootEnemy(){ //lets print which type of soldier is shooting Log.i(soldierType, " is shooting"); } }
- Now that we have a class, a blueprint for our future objects of the
Soldier
type, we can start to build our army. In the editor window, click on the tab of MainActivity.java. We will write this code, as so often, within theonCreate
method just after the call tosetContentView
://first we make an object of type soldier Soldier rambo = new Soldier(); rambo.soldierType = "Green Beret"; rambo.health = 150;// It takes a lot to kill Rambo //Now we make another Soldier object Soldier vassily = new Soldier(); vassily.soldierType = "Sniper"; vassily.health = 50;//Snipers have less armor //And one more Soldier object Soldier wellington = new Soldier(); wellington.soldierType = "Sailor"; wellington.health = 100;//He's tough but no green beret
Tip
This is a really good time to start taking advantage of the autocomplete feature in Android Studio. Notice that after you have declared and created a new object, all you have to do is begin typing the object's name and all the autocomplete options will present themselves.
- Now that we have our extremely varied and somewhat unlikely army, we can use it and also verify the identity of each object. Type the following code below the code in the previous step:
Log.i("Rambo's health = ", "" + rambo.health); Log.i("Vassily's health = ", "" + vassily.health); Log.i("Wellington's health = ", "" + wellington.health); rambo.shootEnemy(); vassily.shootEnemy(); wellington.shootEnemy();
- Now we can run our app on an emulator. Remember, all the output will be in the LogCat console window.
This is how the preceding pieces of code work. In step 2, Android Studio created a template for our new Soldier
class. In step 3, we implemented our class in quite the same way that we have before—two variables, an int
and a string
, called health
and soldierType
, respectively.
We also have a method in our class called shootEnemy
. Let's look at it again and examine what is going on:
void shootEnemy(){ //lets print which type of soldier is shooting Log.i(soldierType, " is shooting"); }
In the body of the method, we print the soldierType
string to the console first, and then the arbitrary " is shooting"
string. What's neat here is that the soldierType
string will be different depending on which object we call the shootEnemy
method on.
In step 4, we declared, created, and assigned three new objects of type Soldier
. They where rambo
, vassily
, and wellington
. In step 5, we initialized each with a different value for health
as well as soldierType
.
Here is the output:
Rambo's health =﹕ 150 Vassily's health =﹕ 50 Wellington's health =﹕ 100 Green Beret﹕ is shooting Sniper﹕ is shooting Sailor﹕ is shooting
Notice that each time we access the health
variable of each Soldier
object, it is printed to the value we assigned it, demonstrating that although the three objects are of the same type, they are completely separate individual objects.
Perhaps more interesting are the three calls to shootEnemy
. One call by each of our Soldier
objects' shootEnemy
method is made, and we print the soldierType
variable to the console. The method has the appropriate value for each individual object, further demonstrating that we have three distinct objects, albeit created from the same Soldier
class.
More things we can do with our first class
We can treat a class much like other variables. Assuming we have already implemented our Soldier
class, we can make an array of Soldier
objects like this:
//Declare an array called myArmy to hold 10 Soldier objects Soldier [] myArmy = new Soldier[10]; //Then we can add the Soldier objects //We use the familiar array notation on the left //And the newly learnt new Soldier() syntax on the right myArmy[0] = new Soldier(); myArmy[1] = new Soldier(); myArmy[2] = new Soldier(); myArmy[3] = new Soldier(); //Initialize more here //..
Then we can use an object from an array using the same style of array notation as we did for regular variables, like this:
myArmy[0].health = 125; myArmy[0].soldierType = "Pilot"; myArmy[0].shootEnemy(); // Pilot﹕ is shooting
We can also use a class as an argument in a method call. Here is a hypothetical call to a healSoldier
method:
healSoldier(rambo); //Perhaps healSoldier could add to the health instance variable
Tip
Of course, the preceding example might raise questions like should the healSoldier
method be a method of a class?
someHospitalObjectPerhaps.healSoldier(rambo);
It could be or not (as shown in the previous example). It would depend upon what is the best solution for the situation. We will look at more OOP, and then the best solution for lots of similar conundrums should present themselves more easily.
As you might have come to expect by now, we can use an object as the return value of a method. Here is what the hypothetical healSoldier
method might look like:
Soldier healSoldier(Soldier soldierToBeHealed){ soldierToBeHealed.health++; return soldierToBeHealed; }
All of this information will likely raise a few questions. OOP is like that, so to try and consolidate all this class stuff with what we already know, let's take another look at variables and encapsulation.
Encapsulation
So far, what we have really seen is what amounts to a kind of code-organizing convention, although we did discuss the wider goals of all this OOP stuff. Now we will take things further and begin to see how we actually manage to achieve encapsulation with OOP.
Tip
Definition of encapsulation
As we have learned encapsulation means keeping the internal workings of your code safe from interference from the programs that use it, allowing only the variables and methods you choose to be accessed. This means your code can always be updated, extended, or improved without affecting the programs that use it, as long as the exposed parts are still made available in the same way. It also allows the code that uses your encapsulated code to be much simpler and easier to maintain because much of the complexity of the task is encapsulated in your code.
But didn't I say that we don't have to know what is going on inside? So you might question what we have seen so far. If we are constantly setting the instance variables like this rambo.health = 100;
, isn't it possible that eventually things could start to go wrong, perhaps like the following line of code?
rambo.soldierType = "ballerina";
Encapsulation protects your class from being used in a way that it wasn't meant to be. By strictly controlling the way that your code is used, it can only ever do what you want it to do, with values you can control. It can't be forced into errors or crashes. Also, you are then free to make changes to the way your code works internally, without breaking any programs that are using an older version of the code or the rest of your program:
weighlifter.legstrength = 100; weighlifter.armstrength = -100; weightlifter.liftHeavyWeight(); //one typo and weightlifter rips own arms off
We can encapsulate our classes to avoid this, and here is how.
Controlling the use of classes with access modifiers
The designer of the class controls what can be seen and manipulated by any program that uses their class. We can add an access modifier before the class
keyword, like this:
public class Soldier{
//Implementation goes here
}
There are two class access modifiers. Let's briefly look at each in turn:
public
: This is straightforward. A class declared aspublic
can be seen by all other classes.default
: A class has default access when no access modifier is specified. This will make it public but only to classes in the same package, and inaccessible to all others.
Now we can make a start with this encapsulation thing. However, even at a glance, the access modifiers described are not very fine-grained. We seem to be limited to complete lockdown to anything outside the package or a complete free-for-all.
Actually, the benefits here are easily taken advantage of. The idea would be to design a package that fulfills a set of tasks. Then all the complex inner workings of the package, the stuff that shouldn't be messed with by anybody but our package, should have default access (only accessible to classes within the package). We can then provide a careful selection of public classes that can be used by others (or other distinct parts of our program).
Tip
For the size and complexity of the games in this book, multiple packages are almost certainly overkill.
Class access in a nutshell
A well-designed app will probably consist of one or more packages, each containing only default or default and public classes.
In addition to class-level privacy controls, Java gives us very fine-grained controls, but to use these controls, we have to look at variables in more detail.
Controlling the use of variables with access modifiers
To build on class visibility controls, we have variable access modifiers. Here is a variable with the private access modifier being declared:
private int myInt;
Note also that all of our discussion of variable access modifiers applies to object variables too. For example, here is an instance of our Soldier
class being declared, created, and assigned. As you can see, the access specified in this case is public:
public Soldier mySoldier = new Soldier();
Before you apply a modifier to a variable, you must first consider the class visibility. If class a
is not visible to class b
, say because class a
has default access and class b
is in another package, then it doesn't make any difference what access modifiers you use on the variables in class a
; class b
still can't see it.
Thus, it makes sense to show a class to another class when necessary, but you should only expose the variables that are needed—not everything.
We have a bit more to cover on access modifiers, and then we will look at a few examples to help clarify things. For now, here is an explanation of the different variable access modifiers. They are more numerous and fine-grained than the class access modifiers. Most of the explanations are straightforward, and the ones that might raise questions will become clearer when we look at an example.
The depth and complexity of access modification is not so much in the range of modifiers, but by using them in smart ways, we can combine them to achieve the worthy goals of encapsulation. Here are the variable access modifiers:
public
: You guessed it! Any class or method from any package can see this variable. Usepublic
only when you are sure that this is what you want.protected
: This is the next least restrictive modifier afterpublic
.protected
Variables set as protected can be seen by any class and any method as long as they are in the same package.default
: This doesn't sound as restrictive asprotected
, but it is more so. A variable has default access when no access is specified. The fact thatdefault
is restrictive perhaps implies that we should be thinking on the side of hiding our variables rather than exposing them. At this point, we need to introduce a new concept. Do you remember that we briefly discussed inheritance, and how we can quickly take on the attributes of a class and yet refine it using theextends
keyword? Just for the record, default access variables are not visible to subclasses. This means that when we extend a class like we did withActivity
, we cannot see its default variables. We will look at inheritance in more detail later in the chapter.private
: These variables can only be seen within the class they are declared. Like default access, they cannot be seen by subclasses (inherited classes).
Variable access in a nutshell
A well-designed app will probably consist of one or more packages, each containing only default or default and public classes. Within these classes, variables will have carefully chosen and most likely varied access modifiers.
There's one more twist in all this access modification stuff before we get practical with it.
Methods have access modifiers too
It makes sense that methods are the things that our classes can do. We will want to control what users of our class can and can't do. The general idea here is that some methods will do things internally only and are therefore not needed by users of the class, and some methods will be fundamental to how users use your class.
The access modifiers for methods are the same as those for the class variables. This makes things easy to remember but suggests again that successful encapsulation is a matter of design rather than any specific set of rules.
As an example, the method in the following code snippet, provided in a public class, can be used by any other class:
public useMeEverybody(){ //do something everyone needs to do here }
However, the following method can only be used internally by the class that created it:
private secretInternalTask(){ //do something that helps the class function internally //Perhaps, if it is part of the same class, //useMeEverybody could use this method... //On behalf of the classes outside of this class. //Neat! }
The next method has default visibility with no access specified. It can be used only by other classes in the same package. If we extend the class containing this default access method, the class will not have access to this method:
fairlySecretTask(){ //allow just the classes in the package //Not for external use }
Here is a last example before we move on. It contains a protected
method, only visible to the package, but usable by our classes that extend it:
protected familyTask(){ //allow just the classes in the package //And you can use me if you extend me too }
Method access in a nutshell
Method access should be chosen to best enforce the principles we have already discussed. It should provide the users of your class with just the access they need, and preferably nothing more. Thereby, we achieve our encapsulation goals such as keeping the internal workings of your code safe from interference from the programs that use it, for all the reasons we have discussed.
Accessing private variables with the getter and setter methods
So if it is best practice to hide our variables away as private, how do we allow access to them without spoiling our encapsulation? What if an object of the Hospital
class wanted access to the health
member variable from an object of type Soldier
so that it could increase it? The health
variable should be private, right?
In order to be able to make as many member variables as possible private and yet allow some kind of limited access to some of them, we use getters and setters. Getters and setters are methods that just get and set variable values.
This is not some special or new Java thing we have to learn. It is just a convention for the use of what we already know. Let's take a look at getters and setters using the example of our Soldier
and Hospital
classes.
In this example, each of our two classes are created in their own file but the same package. First of all, here is our hypothetical Hospital
class:
class Hospital{ private void healSoldier(Soldier soldierToHeal){ int health = soldierToHeal.getHealth(); health = health + 10; soldierToHeal.setHealth(health); } }
Our implementation of the Hospital
class has just one method, healSoldier
. It receives a reference to a Soldier
object as a parameter, so this method will work on whichever Soldier
object is passed in: vassily
, wellington
, rambo
, or whoever.
It also has a health
variable. It uses this variable to temporarily hold and increase the soldier's health. In the same line, it initializes the health
variable to the Soldier
object's current health. The Soldier
object's health
is private, so the public getter method is used instead.
Then health
is increased by 10 and the setHealth
setter method loads the new health
value back to the Soldier
object.
The key here is that although a Hospital
object can change a Soldier
object's health, it does so within the bounds of the getter and setter methods. The getter and setter methods can be written to control and check for potentially erroneous or harmful values.
Next comes our hypothetical Soldier
class, with the simplest implementation possible of it's getter and setter methods:
public class Soldier{ private int health; public int getHealth(){ return health; } public void setHealth(int newHealth){ health = newHealth; } }
We have one instance variable called health
and it is private. Private means it can only be changed by methods of the Soldier
class. We then have a public getHealth
method, which unsurprisingly returns the value held in the private health
variable of the int
type. As this method is public, anyone with access to the Soldier
class can use it.
Next, the setHealth
method is implemented. Again it is public, but this time, it takes int
as a parameter and assigns whatever value is passed to the private health
variable. In a more life-like example, we would write some more code here to ensure that the value passed is within the bounds we expect.
Now we will declare, create, and assign to make an object of each of our two new classes and see how our getters and setters work:
Soldier mySoldier = new Soldier(); //mySoldier.health = 100;//Doesn't work private //we can use the public setter setHealth() mySoldier.setHealth(100);//That's better Hospital militaryHospital = new Hospital(); //Oh no mySoldier has been wounded mySoldier.setHealth(10); //Take him to the hospital //But my health variable is private //And Hospital won't be able to access it //I'm doomed - tell Laura I love her //No wait- what about my public getters and setters? //We can use the public getters and setters from another class militaryHospital.healSoldier(mySoldier); //mySoldiers private variable health has been increased by 10 //I'm feeling much better thanks!
We see that we can call our public setHealth
and getHealth
methods directly on our object of type Soldier
. Not only that, we can also call the healSoldier
method of the Hospital
object, passing in a reference to the Soldier
object, which can use the public getters and setters to manipulate the private health
variable.
We see that the private health
variable is simply accessible, yet totally within the control of the designer of the Soldier
class.
If you want to play around with this example, there is a working app in the code bundle in the Chapter6
folder, called Getters And Setters
. I have added a few lines of code to print to the console. We deliberately covered this the way we did to keep the key parts of the code as clear as possible. We will soon build some real working examples that explore class, variable, and method access.
Note
Getters and setters are sometimes referred to by their more correct names, Accessors and Mutators. We will stick to getters and setters. Just thought you might like to know.
Yet again, our example and the explanation are probably raising more questions. That's good! Previously, I said that:
- There are two access modifiers for a class, default and public
- Objects of classes are a type of reference variable
- Variables (including objects) have even more access possibilities
We need to look more closely at reference and primitive variables as well as local and instance variables. We will do so in a moment in the Variables revisited section. In that section, we will consolidate our information further to get a tighter grip on this OOP stuff. First let's remind ourselves of a bit about encapsulation.
Using encapsulation features (such as access control) is like signing a really important deal about how to use and access a class, its methods, and its variables. The contract is not just an agreement about the present but an implied guarantee for the future. We will see that as we proceed through this chapter, there are more ways that we refine and strengthen this contract.
Note
It is perfectly possible to rewrite every example in this book without thinking or caring about encapsulation. In fact, the projects in this book outside of this chapter are quite lax about encapsulation.
Use encapsulation where it is needed or, of course, if you are being paid to use it by an employer. Often encapsulation is overkill on small learning projects, such as the games in this book, except when the topic you are learning is encapsulation itself.
We are learning this Java OOP stuff under the assumption that you will one day want to write much more complex apps, whether on Android or some other platform that uses OOP.
Setting up our objects with constructors
With all of these private variables and their getters and setters, does it mean that we need a getter and a setter for every private variable? What about a class with lots of variables that need initializing at the start? Think about this:
mySoldier.name mysoldier.type mySoldier.weapon mySoldier.regiment ...
We could go on like this. Some of these variables might need getters and setters, but what if we just want to set things up when the object is first created to make the object function correctly? Do we need two methods (a getter and a setter) for each?
For this, we have a special method called a constructor. Here, we create an object of type Soldier
and assign it to an object called mySoldier
:
Soldier mySoldier = new Soldier();
There's nothing new here, but look at the last part of that line of code:
...Soldier();
This looks suspiciously like a method.
We have called a special method called a constructor that has been supplied automatically for us by the compiler.
However (and this is getting to the point now), like a method, we can override it, which means we can do really useful things to set up our new object before it is used and any of its methods are placed on the stack:
public Soldier(){ health = 200; //more setup here }
This is a constructor. It has a lot of syntactical similarities to a method. It can only be run with the use of the new
keyword. It is created for us automatically by the compiler unless we create our own like in the previous code.
Constructors have the following properties:
- They have no return type
- They have the same name as the class
- They can have parameters
- They can be overloaded
We will play with constructors in the next demo.
Variables revisited
You probably remember, back in the math game project, that we kept changing where we declared our variables. First, we declared some in onCreate
, then we moved them to just below the class declaration, and then we were making them member or instance variables.
Because we didn't specify the access, they were of default access and visible to the whole class, and as everything took place in the one class, we could access them everywhere. For example, we could update our TextView type objects from onClick
, but why couldn't we do that when they were declared in onCreate
? Further explanation about when and how we can access different variables is probably going to be useful.
The stack and the heap
The VM inside every Android device takes care of memory allocation to our games. In addition, it stores different types of variables in different places.
Variables that we declare and initialize in methods are stored on the area of memory known as the stack
. We can stick to our warehouse analogy when talking about the stack—almost. We already know how we can manipulate the stack.
Let's talk about the heap and what is stored there. All reference type objects, which include objects (of classes) and arrays, are stored in the heap. Think of the heap as a separate area of the same warehouse. The heap has lots of floor space for odd-shaped objects, racks for smaller objects, lots of long rows with smaller sized cube-shaped holes for arrays, and so on. This is where our objects are stored. The problem is that we have no direct access to the heap.
Let's look again at what exactly a reference variable is. It is a variable that we refer to and use via a reference. A reference can be loosely (but usefully) defined as an address or location. The reference (address or location) of the object is on the stack. When we use the dot operator, we are asking Dalvik to perform a task at a specific location as stored in the reference.
Note
Reference variables are just that—a reference. They are a way to access and manipulate the object (variables or methods), but they are not the actual variable. An analogy might be that primitives are right there (on the stack) but references are an address, and we say what to do at the address. In this analogy, all addresses are on the heap.
Why would we ever want a system like this? Just give me my objects on the stack already!
A quick break to throw out the trash
Remember way back in the first chapter when I said Java was easier to learn than some languages because it helps us manage the memory? Well, this whole stack and heap thing does that for us.
As we know, the VM keeps track of all our objects for us and stores them in the heap—a special area of our warehouse. Periodically, the VM will scan the stack, or the regular racks of our warehouse, and match references to objects. If it finds any objects without a matching reference, it destroys them. In Java terminology, it performs garbage collection. Think of a very discriminating refuse vehicle driving through the middle of our heap, scanning objects to match to references. No reference? You're garbage now! After all, if an object has no reference variable, we can't possibly do anything with it anyway. This system of garbage collection helps our games run more efficiently by freeing unused memory.
So variables declared in a method are local, on the stack, and only visible within the method they were declared. A member variable is on the heap and can be referenced from any place where there is a reference to it, provided the access specification allows the referencing.
Now we can take a closer look at the variable scope—what can be seen from where.
There are more twists and turns to be learned with regard to variables. In the next demo, we will explore all we have learned so far in this chapter and some new ideas too.
We will look at the following topics:
- Static variables that are consistent (the same) across every instance of a class
- Static methods of a class where you can use the methods of a class without an object of that class type
- We will demonstrate the scope of class and local variables, and where they can and can't be seen by different parts of the program
- We will look at the
this
keyword, which allows us to write code that refers to variables that belong to a specific instance of a class, but without keeping track of which instance we are currently using
The following is the demo.
Access, scope, this, static, and constructors demo
We have looked at the intricate way by which access to variables and their scope is controlled, and it would probably serve us well to look at an example of them in action. These will not be very practical real-world examples of variable use, but more of a demonstration to help understand access modifiers for classes, methods, and variables, alongside the different types of variables such as reference (or primitive) and local (or instance). Then we will cover the new concepts of static and final variables and the this
keyword. The completed project is in the Chapter6
folder of the code download. It is called AccessScopeThisAndStatic
. We will now perform the following steps to implement it:
- Create a new blank activity project and call it
AccessScopeThisAndStatic
. - Create a new class by right-clicking on the existing
MainActivity
class in the Project Explorer and navigating to New | Class. Name the new classAlienShip
. - Now we declare our new class and some member variables. Note that
numShips
is private and static. We will soon see how this variable is the same across all instances of the class. TheshieldStrength
variable isprivate
andshipName
ispublic
:public class AlienShip { private static int numShips; private int shieldStrength; public String shipName;
- Next is the constructor. We can see that the constructor is public, has no return type, and has the same name as the class, as per the rules. In it, we increment the private static
numShips
variable. Remember that this will happen each time we create a new object of theAlienShip
type. The constructor also sets a value for theshieldStrength
private variable using the privatesetShieldStrength
method:public AlienShip(){ numShips++; //Can call private methods from here because I am part //of the class //If didn't have "this" then this call might be less clear //But this "this" isn't strictly necessary this.setShieldStrength(100); //Because of "this" I am sure I am setting //the correct shieldStrength }
- Here is the public static getter method that classes outside
AlienShip
can use to find out how manyAlienShip
objects are there. We will also see the unusual way in which we use static methods:public static int getNumShips(){ return numShips; }
- The following code shows our private
setShieldStrength
method. We could have just setshieldStrength
directly from within the class, but this code shows how we can distinguish between theshieldStrength
local variable/parameter and theshieldStrength
member variable using thethis
keyword:private void setShieldStrength(int shieldStrength){ //"this" distinguishes between the //member variable shieldStrength //And the local variable/parameter of the same name this.shieldStrength = shieldStrength; }
- This next method is the getter, so other classes can read but not alter the shield strength of each
AlienShip
object:public int getShieldStrength(){ return this.shieldStrength; }
- Now we have a public method that can be called every time an
AlienShip
object is hit. It just prints to the console and then checks whether that particular object'sshieldStrength
is zero. If it is zero, it calls thedestroyShip
method, which we look at next:public void hitDetected(){ shieldStrength -=25; Log.i("Incoming: ","Bam!!"); if (shieldStrength == 0){ destroyShip(); } }
- Finally, we will look at the
destroyShip
method for ourAlienShip
class. We print a message that indicates which ship has been destroyed, based on itsshipName
, as well as increment thenumShips
static variable so that we can keep track of the number of objects of theAlienShip
type we have:private void destroyShip(){ numShips--; Log.i("Explosion: ", ""+this.shipName + " destroyed"); } }
- Now we switch over to our
MainActivity
class and write some code that uses our newAlienShip
class. All of the code goes in theonCreate
method after the call tosetContentView
. First, we create two newAlienShip
objects calledgirlShip
andboyShip
://every time we do this the constructor runs AlienShip girlShip = new AlienShip(); AlienShip boyShip = new AlienShip();
- Look how we get the value in
numShips
. We use thegetNumShips
method as we might expect. However, look closely at the syntax. We are using the class name and not an object. We can also access static variables with methods that are not static. We did it this way to see a static method in action://Look no objects but using the static method Log.i("numShips: ", "" + AlienShip.getNumShips());
- Now we assign names to our public
shipName
string variables://This works because shipName is public girlShip.shipName = "Corrine Yu"; boyShip.shipName = "Andre LaMothe";
- If we attempt to assign a value directly to a private variable, it won't work. Therefore, we use the public
getShieldStrength
getter method to print the value ofshieldStrength
, which was assigned to the constructor://This won't work because shieldStrength is private //girlship.shieldStrength = 999; //But we have a public getter Log.i("girlShip shieldStrngth: ", "" + girlShip.getShieldStrength()); Log.i("boyShip shieldStrngth: ", "" + boyShip.getShieldStrength()); //And we can't do this because it's private //boyship.setShieldStrength(1000000);
Finally, we get to blow some stuff up by playing with the
hitDetected
method and occasionally checking the shield strength of our two objects://let's shoot some ships girlShip.hitDetected(); Log.i("girlShip shieldStrngth: ", "" + girlShip.getShieldStrength()); Log.i("boyShip shieldStrngth: ", "" + boyShip.getShieldStrength()); boyShip.hitDetected(); boyShip.hitDetected(); boyShip.hitDetected(); Log.i("girlShip shieldStrngth: ", "" + girlShip.getShieldStrength()); Log.i("boyShip shieldStrngth: ", "" + boyShip.getShieldStrength()); boyShip.hitDetected();//ahhh Log.i("girlShip shieldStrngth: ", "" + girlShip.getShieldStrength()); Log.i("boyShip shieldStrngth: ", "" + boyShip.getShieldStrength());
- When we think we have destroyed a ship, we again use our static
getNumShips
method to check whether our static variablenumShips
was changed by thedestroyShip
method:Log.i("numShips: ", "" + AlienShip.getNumShips());
- Run the demo and look at the console output.
Here is the output of the preceding blocks of code:
numShips:﹕ 2 girlShip shieldStrngth:﹕ 100 boyShip shieldStrngth:﹕ 100 Incomiming:﹕ Bam!! girlShip shieldStrngth:﹕ 75 boyShip shieldStrngth:﹕ 100 Incomiming:﹕ Bam!! Incomiming:﹕ Bam!! Incomiming:﹕ Bam!! girlShip shieldStrngth:﹕ 75 boyShip shieldStrngth:﹕ 25 Incomiming:﹕ Bam!! Explosion:﹕ Andre LaMothe destroyed girlShip shieldStrngth:﹕ 75 boyShip shieldStrngth:﹕ 0 numShips:﹕ 1 boyShip shieldStrngth:﹕ 0 numShips:﹕ 1
In the previous example, we saw that we can distinguish between local and member variables of the same name using the this
keyword. We can also use the this
keyword to write code that refers to the current object being acted upon.
We saw that a static variable, in this case numShips
, is consistent across all instances. Moreover, by incrementing it in the constructor and decrementing it in our destroyShip
method, we can keep track of the number of AlienShip
objects we created.
We also saw that we can use static methods by writing the class name with the dot operator instead of an object.
Finally, we demonstrated how we could hide and expose certain methods and variables using an access specifier.
Let's take a look at a quick review of the stack and the heap before we move on to something new.
A quick summary on stack and heap
Let's look at what we learned about the stack and the heap:
- You don't delete objects but the VM sends the garbage collector when it thinks it is appropriate. This is usually done when there is no active reference to the object.
- Local variables and methods are on the stack, and local variables are local to the specific method within which they were declared.
- Instance or class variables are on the heap (with their objects) but the reference to the object (address) is a local variable on the stack.
- We control what goes inside the stack. We can use the objects on the heap but only by referencing them.
- The heap is maintained by the garbage collector.
- An object is garbage collected when there is no longer a valid reference to it. Therefore, when a reference variable, local or instance, is removed from the stack, then its related object becomes viable for garbage collection, and when the virtual machine decides the time is right (usually very promptly), it will free the RAM memory to avoid running out.
- If we try to reference an object that doesn't exist, we will get a null pointer exception and the game will crash.
Inheritance
We have seen how we can use other people's hard work by instantiating/creating objects from the classes of an API such as that of Android, but this whole OOP thing goes even further than that.
What if there is a class that has loads of useful functionality in it but not quite what we want? We can inherit from the class and then further refine or add to how it works and what it does.
You might be surprised to hear that we have done this already. In fact, we have done this with every single game and demo we looked at. When we use the extends
keyword, we are inheriting, for example, in this line of code:
public class MainActivity extends Activity ...
Here, we are inheriting the Activity
class along with all its functionality, or more specifically, all of the functionality that the class designers want us to have access to. Here are some of the things we can do to classes we have extended.
We can override a method and still rely in part on the overridden method in the class we inherit from. For example, we overrode the onCreate
method every time we extended the Activity
class, but we also called the default implementation provided by the class designers when we did this:
super.onCreate(...
In the next chapter, we will also be overriding some more methods of the Activity
class. Specifically, we'll override the methods that handle the lifecycle.
If we or the designer of a class wants to force us to inherit before we use their class, they can declare a class as abstract. Then we cannot make an object from it. Therefore, we must extend it first and make an object from the subclass. We will do this in our inheritance example and discuss it further when we look at polymorphism.
We can also declare a method abstract, and that method must be overridden in any class that extends the class with the abstract method. We will do this as well in our inheritance example.
In our game projects, we will not be designing any classes that we will be extending. We have no need of that in the context of learning about building simple games. However, we will be extending classes designed by others in every future game.
We discuss inheritance mainly so that we understand what is going on around us and as the first step towards being able to eventually design useful classes that we or others can extend. With this in mind, let's make some simple classes and see how we can extend them, just to play around with the syntax as a first step, and also to be able to say we have done it. When we look at the last major topic of this chapter, polymorphism, we will also dig a little deeper into inheritance.
An example of inheritance
We have looked at the way we can create hierarchies of classes to model the system that fits our game or software project, so let's try out some simple code that uses inheritance. The completed project is in the Chapter6
folder of the code download. It is called InheritanceExample
. We will now perform the following steps:
- Create three new classes in the usual way. Call one
AlienShip
, anotherFighter
, and the last oneBomber
. - Here is the code for the
AlienShip
class. It is very similar to our previousAlienShip
class demo. The differences are that the constructor now takes anint
parameter, which it uses to set the shield strength. The constructor also outputs a message to the console so that we can see when it is being used. TheAlienShip
class also has a new method,fireWeapon
, that is declaredabstract
. This guarantees that any class that subclassesAlienShip
must implement their own version offireWeapon
. Notice that the class has theabstract
keyword as part of its declaration. We have to do this because one of its methods also uses the keywordabstract
. We will explain theabstract
method when discussing this demo and theabstract
class when we talk about polymorphism:public abstract class AlienShip { private static int numShips; private int shieldStrength; public String shipName; public AlienShip(int shieldStrength){ Log.i("Location: ", "AlienShip constructor"); numShips++; setShieldStrength(shieldStrength); } public abstract void fireWeapon();//Ahh my body public static int getNumShips(){ return numShips; } private void setShieldStrength(int shieldStrength){ this.shieldStrength = shieldStrength; } public int getShieldStrength(){ return this.shieldStrength; } public void hitDetected(){ shieldStrength -=25; Log.i("Incomiming: ", "Bam!!"); if (shieldStrength == 0){ destroyShip(); } } private void destroyShip(){ numShips--; Log.i("Explosion: ", "" + this.shipName + " destroyed"); } }
- Now we will implement the
Bomber
class. Notice the call tosuper(100)
. This calls the constructor of the superclass with the value forshieldStrength
. We could do further specificBomber
initialization in this constructor, but for now, we just print the location so that we can see when theBomber
constructor is being executed. We also implement aBomber
class-specific version of the abstractfireWeapon
method because we must do so:public class Bomber extends AlienShip { public Bomber(){ super(100); //Weak shields for a bomber Log.i("Location: ", "Bomber constructor"); } public void fireWeapon(){ Log.i("Firing weapon: ", "bombs away"); } }
- Now we will implement the
Fighter
class. Notice the call tosuper(400)
. This calls the constructor of the superclass with the value ofshieldStrength
. We could do furtherFighter
class-specific initialization in this constructor, but for now, we just print the location so that we can see when theFighter
constructor is being executed. We also implement aFighter
specific version of the abstractfireWeapon
method because we must do so:public class Fighter extends AlienShip{ public Fighter(){ super(400); //Strong shields for a fighter Log.i("Location: ", "Fighter constructor"); } public void fireWeapon(){ Log.i("Firing weapon: ", "lasers firing"); } }
- Here is our code in the
onCreate
method ofMainActivity
. As usual, we enter this code after the call tosetContentView
. This is the code that uses our three new classes. It looks quite ordinary, but there's nothing new; it is the output that is interesting:Fighter aFighter = new Fighter(); Bomber aBomber = new Bomber(); //Can't do this AlienShip is abstract - //Literally speaking as well as in code //AlienShip alienShip = new AlienShip(500); //But our objects of the subclasses can still do //everything the AlienShip is meant to do aBomber.shipName = "Newell Bomber"; aFighter.shipName = "Meier Fighter"; //And because of the overridden constructor //That still calls the super constructor //They have unique properties Log.i("aFighter Shield:", ""+ aFighter.getShieldStrength()); Log.i("aBomber Shield:", ""+ aBomber.getShieldStrength()); //As well as certain things in certain ways //That are unique to the subclass aBomber.fireWeapon(); aFighter.fireWeapon(); //Take down those alien ships //Focus on the bomber it has a weaker shield aBomber.hitDetected(); aBomber.hitDetected(); aBomber.hitDetected(); aBomber.hitDetected();
Here is the output of the preceding snippets of code:
Location:﹕ AlienShip constructor Location:﹕ Fighter constructor Location:﹕ AlienShip constructor Location:﹕ Bomber constructor aFighter Shield:﹕ 400 aBomber Shield:﹕ 100 Firing weapon:﹕ bombs away Firing weapon:﹕ lasers firing Incomiming:﹕ Bam!! Incomiming:﹕ Bam!! Incomiming:﹕ Bam!! Incomiming:﹕ Bam!! Explosion:﹕ Newell Bomber destroyed
We can see how the constructor of the subclass can call the constructor of the superclass. We can also clearly see that the individual implementations of the fireWeapon
method work exactly as expected.
As if OOP where not useful enough already! We can now model real-world objects and design them to interact with each other. We have also seen how we can make OOP even more useful by subclassing/extending/inheriting from other classes. The terminology we might like to learn here is that the class that is extended from is the superclass and the class that inherits from the superclass is the subclass. We can also call them parent and child classes.
Tip
As usual, we might find ourselves asking this question about inheritance: Why? We can write common code once, in the parent class, and we can update that common code. All the classes that inherit from it are also updated. Furthermore, a subclass only inherits public instance variables and methods. When designed properly, this further enhances the goals of encapsulation.
Polymorphism
Polymorphism roughly means different forms. But what does it mean to us?
In the simplest words possible, any subclass can be used as a part of the code that uses the superclass.
For example, if we have an array of animals, we could put any object that is of a type that is a subclass of Animal
in the Animal
array, perhaps cats and dogs.
This means that we can write code that is simpler and easier to understand and modify:
//This code assumes we have an Animal class //And we have a Cat and Dog class that extends Animal Animal myAnimal = new Animal(); Dog myDog = new Dog(); Cat myCat = new Cat(); Animal [] myAnimals = new Animal[10]; myAnimals[0] = myAnimal;//As expected myAnimals[1] = myDog;//This is OK too myAnimals[2] = myCat;//And this is fine as well
We can also write code for the superclass and rely on the fact that no matter how many times it is subclassed, within certain parameters, the code will still work. Let's continue our previous example:
//6 months later we need elephants //with its own unique aspects //As long as it extends Animal we can still do this Elephant myElephant = new Elephant(); myAnimals[3] = myElephant;//And this is fine as well
You can also write methods with polymorphic return types and arguments:
Animal feedAnimal(Animal animalToFeed){ //Feed any animal here return animalToFeed; }
So you can even write code today and make another subclass in a week, month, or year, and the very same methods and data structures will still work.
Further, we can enforce on our subclasses a set of rules as to what they can and cannot do, and also how they should do it. Thus, good design in one stage can influence our subclasses at other stages.
If you do suddenly find yourself with a flappy-bird-sized phenomenon, and you have a lot of OOP in your code, right from the start, it will be much easier to bring in hired help to move the project forward and still maintain control of the project.
What if you have an idea for a game with lots of features but you want to get a simple version of the game out as soon as possible? Smart, object-oriented design would certainly be the solution. It could enable you to write the working bare bones of a game and then gradually extend it.
Moving on, let's look at another OOP concept: abstract classes. We can now get to the bottom of what was going on with that AlienShip
code:
public abstract class AlienShip{...
Abstract classes
An abstract class is a class that cannot be instantiated, or cannot be made into an object. We mentioned that AlienShip
was abstract in the previous example. So is it a blueprint that will never be used then? But that's like paying an architect to design your home and then never building it! I kind of got the idea of an abstract method but this is just silly!
It might seem like this at first. We make a class abstract by declaring it with the abstract
keyword, like this:
abstract class someClass{ //All methods and variables here as usual //Just don't try and make an object out of me! }
But why?
Sometimes, we want a class that can be used as a polymorphic type but we need to ensure that it can never be used as an object. For example, Animal
doesn't really make sense on its own.
We don't talk about animals; we talk about types of animals. We don't say, "Ooh, look at that lovely, fluffy, white animal", or "Yesterday, we went to the pet shop and got an animal and an animal bed." It's just too abstract.
So an abstract class is like a template to be used by any class that extends it (inherits from it).
We might want a Worker
class and extend to make classes such as Miner
, Steelworker
, OfficeWorker
, and of course Programmer
. But what exactly does a plain Worker
class do? Why would we ever want to instantiate one?
The answer is that we wouldn't want to instantiate it, but we might want to use it as a polymorphic type so that we can pass multiple worker subclasses between methods and have data structures that can hold all types of workers.
We call this type of class an abstract class, and when a class has even one abstract method, like AlienShip
did, it must be declared abstract itself. As we saw, all abstract methods must be overridden by any class that extends the abstract class. This means that the abstract class can provide some of the common functionality that would be available in all its subclasses. For example, the Worker
class might have the height
, weight
, and age
member variables.
It might have the getPayCheck
method, which is the same in all the subclasses, and the doWork
method, which is abstract and must be overridden because all the different types of worker do work very differently.
This leads us neatly on to another area of polymorphism that deserves an honorable mention because we have been using it in every game so far.
Interfaces
An interface is like a class. Phew! Nothing complicated here then. However, it's like a class that is always abstract and with only abstract methods.
We can think of an interface as an entirely abstract class with all its methods abstract too. Okay, so you can just about wrap your head round an abstract class because it can at least pass on some functionality in its methods that are not abstract and serve as a polymorphic type.
But seriously, this interface seems a bit pointless. Bear with me.
To define an interface, we type the following code:
public interface myInterface{ void someAbstractMethod();//omg I've got no body int anotherAbstractMethod();//Ahh! Me too //Interface methods are always abstract and public implicitly //but we could make it explicit if we prefer public abstract explicitlyAbstractAndPublicMethod();//still no body though }
The methods of an interface have no body because they are abstract, but they can still have return types and parameters, or not.
To use an interface, we use the implements
keyword after the class declaration. Yes, we already did this for onClickListener
a few times:
public class someClass implements someInterface{ //class stuff here //better implement the methods of the interface or the red error lines will not go away public void someAbstractMethod(){ //code here if you like but just an empty implementation will do } public int anotherAbstractMethod(){ //code here if you like but just an empty implementation will do //Must have a return type though as that is part of the contract return 1;} }
This enables us to use polymorphism with multiple different objects that are from completely unrelated inheritance hierarchies. As long as it implements an interface, the whole thing can be passed along as if it is that thing, which it is. We can even have a class implement multiple different interfaces at the same time. Just add a comma between each interface and list them after the implements
keyword. Just be sure to implement all the necessary methods.
Let's go back to the onClickListener
interface. Any thing might like to know when it is being clicked on; a Button, a TextView, and so on. We don't want different onClick
methods for every type.
Tip
When using Android, for games or for more regular GUI-based apps (a bit like ours so far), 9 times out of 10, you will be implementing interfaces rather than writing your own. However, knowing what is happening is quite important, not so much from a point of view of technical awareness, as we have just seen that the interface specifies a contract and the compiler enforces it, but more as a matter of sanity in knowing what is actually happening when you use the implements
keyword and write a method (or methods) with a name that you didn't choose.
More about OOP and classes
It is possible to write a whole book on OOP, and many authors have already done so, but the best way to learn OOP is probably to practice it; practice it before we have learned all of the theory. Anyway, before we get on with some more practical examples, here is one more slightly theoretical OOP example that will leave us scratching our heads later if not mentioned.
Inner classes
When we looked at our basic classes demo app, we declared and implemented the class in a separate file to our MainActivity
class. That file had the same name as the class.
We can also declare and implement a class within a class. The only question remaining, of course, is why would we do this? When we implement an inner class, the inner class can access the member variables of the enclosing class and the enclosing class can access the members of the inner class. We will see this in action in the next two chapters.
If you are not modeling deep or real-world systems, then inner classes are often the way to go. In fact, all the classes we will write ourselves in the rest of this book will be extended inner classes. This means that we will extend a type to make our own class within our Activity
class. This makes our code nice and simple.
Self-test questions
Q1) Find out what is wrong with this class declaration:
private class someClass{ //class implementation goes here }
Q2) What is encapsulation?
Q3) I don't get it all, and actually, I have more questions now than I had at the start of the chapter. What should I do?
Summary
In this chapter, we covered more theory than in any other chapter. If you haven't memorized everything, then you have succeeded completely. If you just understand that OOP is about writing reusable, extendable, and efficient code through encapsulation, inheritance, and polymorphism, then you have the potential to be a Java master. Simply put, OOP enables us to use other people's hard work even when those people were not aware of exactly what we would be doing at the time they did the work. All you have to do is keep practicing, so let's make a retro game in the next chapter.