Basic Object-Oriented Programming in Java
Optional Reading
Java Tutorial on Classes and Objects
Important note on images in this module
Please note that the screenshots in this section were made with an earlier version of the JDK and Elipse. Since everything covered in this module still works the same in the latest JDK/Eclipse versions, they have not been updated as they fully apply.
Intro
Before we start, let's cover the difference between a Class, Interface and an Object. We will cover each in more detail, but I think it's good to keep the differences in mind from the beginning.
"Class" means a category of things, you can also think of it as a template to use to create a set of very similar items, with basic properties, which may have variations between each item. Sometimes, these categories or templates are used directly without using Objects by using "static" methods which operate directly on the Class.
"Interface" is generally a "contract" for methods that a Class/Object will support. It basically supports multiple inheritance (sort of), and in Java 8, it technically can support multiple inheritance with "default" methods.
"Object" means a specific item created from a "Class". It is also referred to as an "instance" of a "Class".
Naming Conventions
Variables and classes within Java are typically (and should be) formed by concatenating multiple words into a single string, capitalizing each first letter of each word. If the variable is an attribute of the class, the first letter is not capitalized and is left as lower case. Avoid using the "_" character to create variable names, and do not use it for class names. As with all rules, there is one exception...Java supports variables that are constants, that is their value cannot be changed. To signify a variable is a constant, it is written in all-caps with underscores ("_") between he words..
Coding Style
This is often a very opinionated area, and actual coding style should often determined by your workplace, but in general:
Indentation: blocks of code that are nested more should be indented more
Good |
Bad |
statement; statement; for(...) { statement; statement; for(...) { statement; statement; } } |
statement; statement; for(...) { statement; statement; for(...) { statement; statement; } } |
Indentation: blocks that are nested the same should be indented the same
Good |
Bad |
statement; statement; for(...) { statement; statement; for(...) { statement; statement; } |
statement; statement; for(...) { statement; statement; for(...) { statement; statement; } } |
Braces and spaces: Number of spaces and placement of braces is a matter of test, just be consistent!
OK |
OK |
OK |
statement; statement; for(...) { statement; statement; for(...) { statement; statement; } } |
statement; statement; for(...) { statement; statement; for(...) { statement; statement; } } |
statement; statement; for(...) { statement; statement; for(...) { statement; statement; } } |
Classes
When defining a class, it is best to keep things aligned with physical or logical traits that can be associated with a real world Object. Technically, there is no right or wrong way to design an object. What you associate with an object is based on what you think will work best for a Class. Unfortunately, I think that experience is the best teacher in this area. As you design an use classes, you begin to get a feel of what works best for you (and hopefully your team). If you make good design decisions, the class is easy to use, easy to subclass and just plain old makes sense.
Instead, if you find yourself fighting a class, it probably means you didn't make the right design decision.
On of the classic examples is the creation of classes that represent a corporations structure. Typically, you want to work from the most basic definition, and work your way "out" to more complex classes.
You declare the definition of a class by defining the code in the following way
class YourObject {
// field declarations // constructor declarations // methods declarations }}
This is the usual pattern to use when you define a class. Keeping to this pattern (fields, first, then constructor, then methods) helps others read your code easier.
You might think to start with an Employee class....but think for a moment....you may have people in your company that aren't employees, but may need to be included in an application (Contractors, Visitors, etc).
So, maybe instead of starting with an Employee, we should start with a Person.
Fire up Netbeans and create a new Project...we'll call it ObjectOrientedOne. Note that I have deselected the "Create Main Class" checkbox
Go ahead and make a new class, call it Person...
We are ready to build our Class, but first, let's talk about some good Class design.
Grady Booch, a pioneer in Object Oriented design, proposed five metrics to measure the quality of a Class.
So, what does this all mean? Basically it means when you make an Object, try to make it stand on its own, and not depend on other Objects. Now this is a goal, not an absolute because many times you do depend on other objects.
So, let's continue with our Person class. Attributes to a class (internal variables), describe the state of a class. Lets come up with some traits for our Person and define them as attributes.
When you define an attribute in a class, you also set it's access modifiers. These control who can view the attribute directly.
Let's set some simple values for a Person...
In "proper" Object Oriented design, internal attributes are marked as private. What this means is that only the defining class can use these variables directly, even a sub-class, which is based on this class, can't see these variables. The reason why you keep your attributes private is that it lets you change the internal structure at any time, without ever compromising class integrity. I'll give an example on this later, but for now, lets talk about the four types of access that Java supports from most to least restrictive.
Okay...great...so if you have something like private access, then how does a different Object get the value? With accessor methods! Again, proper OO design says this is the way to go, if you relax your accessors, you may make things easier now but pay for it at a later date.
As we continue our foray into new Object design, lets make some accessor methods. In Java, there is a standard naming convention for methods that access attributes, and in fact, it is used in things like Java Beans.
Basically, you create "get" and "set" methods that access an attribute. The methods basically put "set" and "get" (or "is") in front of the attribute name, so for the "firstName" attribute, you would create "getFirstName()" and "setFirstName()" methods.
Now, that is definitely generating some boring code...but most IDEs have some type of shortcut that does this for you. So in Eclipse, lets right click in the Person window and select Source->Generate Getters and Setters...
What you see is a way of creating getters and setters for all fields (attributes) of the Class. If we choose Select All....
And then click on the Generate button...
We see that the methods to get and set the attributes are created for us, following the standard Java beans naming conventions.
Ah, but there are several other choices, some of which are often overlooked but either very useful or important. Take a look at the "equals() and hashCode()..." choice.
When you create a class, particularly one like Person which is mostly composed of attributes, you often want to compare it with another instance of this class (equals) or add it to some type of Hash structure.
If you don't define a custom equals() and hashCode() method, you get the default implementations, which are typically NOT what you want. The default implementation of this method in the Object class simply checks if two object references x and y refer to the same object. i.e. It checks if x == y. This particular comparison is also known as "shallow comparison". This means two Object are equal only if they are literally the same Object, but usually we consider two Objects equal if all of their internal attributes are equal even if they have different references.
And now the gotcha, if you define your own equals method, you really need to implement the corresponding hashCode() method. This is because the general contract of hashCode (from the JDK 1.4 API) is:
So, if you have a correct hash code, then anything that uses the hashCode (Hash based collections, will generate a proper hash to the correct object).
Yes, you can do these manually, but Netbeans (and Eclipse) will do them for you with just a few clicks. Let's choose the "equals() and hashCode()..." selection and see what we get.
You need to select which fields will be included in the generation of the methods. In this case, I've chosen all of them (we'll talk later why that might not be appropriate in every case).
You now see the hashCode() and equals() methods generated in the Person class.
This is a really good habit to get into. You always want to generate these methods for your class, and the IDE makes it extremely easy to do so. One other very handy code generator is the toString() method, which is useful for debugging later on.
Basic Object Functionality
Objects are the fundamental building block of Object Oriented Programming. Real-world objects have two basic characteristics, state and behavior. State is usually represented by attributes (also called fields, or even class variables) while behavior is usually represented by methods (also called functions).
A Class definition defines the states and behaviors for a 'class' of Objects. When you instantiate an Object from its Class definition, you finally assign it potentially unique states that differentiate it from other Objects of the same class.
Okay, let's make a few instances of our class to test some of what we have covered out. When you take a Class definition, and make an actual instantiation of the class.
We'll test out the class by making a main() method in the class, and then generating some code there.
public static void main(String[] argv) {
Person p1 = new Person(); p1.setFirstName("Bob"); p1.setLastName("Evans"); Person p2 = p1; Person p3 = new Person(); p2.setFirstName("Bob"); p2.setLastName("Evans"); Person p4 = new Person(); p4.setFirstName("Robert"); p4.setLastName("Evans"); // is p1 == p2? System.out.println("p1 == p2 ? " + (p1 == p2)); System.out.println("p1.equals(p2) ? " + p1.equals(p2)); // is p1 == p3? System.out.println("p1 == p3 ? " + (p1 == p3)); System.out.println("p1.equals(p3) ? " + p1.equals(p3)); // is p1 == p4? System.out.println("p1 == p4 ? " + (p1 == p4)); System.out.println("p1.equals(p4) ? " + p1.equals(p4)); }
Go ahead and add this code to your Person class and run the class as a Java Application. Note that since p1 and p2 are the same object, they return "true" for the "==" test.
Since we defined a custom equals() method, p1 and p3 also return true for the equals() test. As expected p1 and p4 are not equals.
Let's add a few more tests...just to see how things work. Make a new class Test, in the same package as Person, and add the following code:
In this case, I've made a Person object in the Test class, then set the firstName field. Of course I can get the value with the "get" method, but if I try to access the value directly, the IDE shows an error (because firstName has private access).
What if we removed the "private" keyword in front of firstName? Well, that makes it the default access (also knows as "package protected") which says that an Object in the same package can access the attribute.
If you make the change to Person, and then look at Test, you will see the p.firstName call is no longer marked as an error, and if you run the class.
You can see that the Test class now has access to the firstName field of Person.
Transient Attributes
Before we go on, I want to mention one more type of field that doesn't get a lot of coverage, but this is an excellent time to cover it.
If you mark an attribute as "transient", it means that the Object considers the field to be temporary, and not fundamental to the "equality" of the object.
Let's make a transient field called fullName. Full name is a cached value, the first time you call it it creates a full name from the first, middle, last and suffix of the Person, and caches the value in the object. Since this is transient, it lets the system know that it shouldn't be used in equals() and hashCode() methods, as it isn't a fundamental trait of the object that makes it unique. If the object is Serialized, it also tells the system to not bother with the value as it isn't important to the state of the object.
Take a look at the code that implements "getFullName()". In this case, if the transient field "fullName" is null, we calculate the full name and then cache it in the field for future use.
Take note that while the field "fullName" contains data, it isn't "unique" data that defines the state of the object, as it is built on other attributes.
You would not include the value of fullName in the "equals()" or "hashCode()" methods, as again, it isn't uniquely identifying data, it isn't needed to define the state of the Object.
Inheritance
So, one of the points of Object Oriented Programming (OOP) is that object represent real-world things (like the Person above). Another key aspect is that some Objects can share basic traits with other "similar" types of Objects. OOP allows a class to inherit traits and behaviors from a parent (or Super) Class. This allows you to define a base class, then make new subclasses that add more traits and behaviors to the base class, without having to re-implement those traits in the new subclass.
The syntax for creating a subclass is fairly straightforward. When you define a subclass, you declare the name, followed by the "extends" keyword and then the name of the parent (or super) Class.
At this point, we have a Person class with some very basic attributes. We can make an Employee class that inherits from Person, but adds additional traits (a.k.a fields or attributes) and methods (functions).
If we want to define an Employee class, we can say they have everything a Person class has, but also have an employee number and department. (Yes, I'm sure you can think of a lot more attributes, but we are keeping it simple at this point).
Out definition for Class Employee now looks something like this
public class Employee extends Person {
private int employeeNumber;
private String department;
...
Note the use of the extends keyword, and the two new attributes. Now even though Employee inherits from Person, the four attributes we defined for Person aren't directly accessible from the Employee class. The employee class does inherits the methods though.
This means that we cant do something like this in the Employee class...
public void testMethod() {
System.out.println("The first name of the Employee is" + firstName);
}
If we try to do this, the compiler will warn us that class Employee can't access the private field firstName. We can however do this...
public void testMethod() {
System.out.println("The first name of the Employee is" + getFirstName());
}
Because the Employee class inherits the getFirstName() method, so this is valid. Ah, but you may ask "What if we made the firstName field "protected" instead of private?
If you did, then by the inheritance rules, Employee *can* directly access the firstName field.
So, why not make everything protected? This is actually a tough question. By "proper" object oriented principles, it should stay private that way if you wanted to store the name values in a database instead of private fields, you could change the storage in Person without the subclasses ever caring because they stick with the methods that are defined for the class, the internal structure isn't any of their business.
If you are designing classes for maximum re-use, this is the way to go, but it can make maintenance a bit more wordy, and if you know you are designing a base class only to be used by yourself, then using protected (or package protected), may be the way to go.
Returning values from a Method
I mentioned methods earlier, but let's talk a bit more about then before we go any further. In Java, methods are normally considered synchronous, that means that when you call a method, the execution of your program "waits" until the method is completed, returns a value, or throws an exception.
You declare the type of Object (or primitive) that your method will return in its definition. If nothing is returned, then the return value is defined as void.
If a method has a return value, then your code must have a "return <your return type>" statement that can be reached in your code. As an example, if we define a method "add"
public int add(int a, int b) {
return a+b;
} public void resetA(){ a = 0; return; }
In the first method above, we define a method that returns an "int" and the return statement does just that. In the second method, the code resets the value of "a" to 0, and doesn't return a value.
Here is a common issue that people have when defining simple methods. Let's say you have a class defined as "Foo"
public class Foo {
int a=0; public void setA(int a) { a = a;
} }
The code above is designed to take the value passed in the setA() method and assign it to the field "a" in class "Foo". This will compile just fine, but field "a" will never be set! Why? Because the "int a" declaration in the setA() method hides (or shadows) the "a" field for the class. So your "a=a" statement just sets the argument a to itself. If you had said public void setA(final int a), the compiler would have complained and given you a hint.
So, how do you set the "a" field? Well, you could do something old-school like define the method as public void setA(int _a), which works, but frankly looks strange and makes your code harder to read. Or, you can qualify the reference to "a" by using the "this" keyword. "this" is a reference to the current object, and qualifies a reference to a field.
public class Foo {
int a=0; public void setA(int a) { this.a = a;
} }
Class Variables
While we've seen fields that have unique values for each Object instance, there is another category of fields that are common to the Class, rather than unique to the Object.
A class variable (or static field) exists across an entire class. Static variables are strange creatures, when people first start doing Object Oriented programming, they tend to misuse static variables as "shortcuts" to proper design, because they let Object share data across a class.
The issue with a static field is that it exists across all instances of a class. Change it in one spot, it changes everywhere. Let's take a look with our Person object, we'll use two types of static examples.
public class Person { private String firstName; private String middleName; private String lastName; private String suffix; private int age; private transient String fullName; private static int numberOfPeople = 0; public final static int MIN_AGE=18;
public Person() { numberOfPeople++; }
...
Take a look at the numberOfPeople field. This is a very primitive counter on how many Person object have been created. Each time a Person() constructor is called, this value is incremented. Notice that in the constructor, it is referenced like a normal field, but this field can be accessed across any instance of Person, and it will be the same value because it is tied to the Class, not the Object.
While you can use this in this fashion, you will probably find that it is easy to abuse, and difficult to maintain. Be very cautious if you use static fields this way.
The second way is actually quite common. This is how Java would like you to create constants used by a class. Note that we have made this public access, because since the field is final, it is in fact a constant value. Any object can access it by using the Class name followed by the field, e.g. Person.MIN_AGE. I'm sure you notice the use of all Caps in the field name, which is the standard for final static constants. Note that the underline character is used to separate words.
Use of a constant this way is vastly preferred over typing in magic numbers in your code. If you have a later method called:
public boolean isMinor() {
return age < 18;
}
This works, but if you ever change the minimum age to 21, you have to search through all of your code and try to change 18 to 21. If you use MIN_AGE, you only change the value in one place and everywhere that uses it sees the change.
public boolean isMinor() { return age < MIN_AGE; }
Class Methods
Like class variables, you can also have static methods. Static methods differ from Object methods in that the method doesn't care about the state of an Object. Typically you can think of these as "helper" methods that don't use the class instance information, but make sense to associate with a Class name
In computer programming, cohesion refers to the degree to which the elements of a module belong together. High cohesion is preferred, and when you have normal instance attributes and methods, this tends to support a high level of cohesion. The use of static methods is reflective of low object cohesion, and is better than nothing, but still should be acknowledged in your system design. High cohesion tends to reduce Object complexity, increase maintainability, and increase re-usability.
A static method is defined like this
public class Foo {
public static add(int x, int y) { return x+y; } }
You can call the method by qualifying it with the class name, so you would call Foo.add(4, 5) to add 4 and 5 together. Granted this is a very trivial example, but the add() method defined above is an excellent choice for a static method. Why? Because the method has noting to do with object state, it operates solely on the two arguments presented to it. If you static method does rely on a shared non-final object across a class definition, you need to take great care to manage the state correctly.
Interfaces
Interfaces are a sometimes confusing item. Basically, the best way to think about an Interface is that it is a definition of a promise. If a class implements an Interface, it promises to implement the functions defined in the Interface definition. Since it promises to act like the Interface, you can treat the Object that implements the Interface as an Object of Class Interface, although it is not.
Lets make an example.
First we'll define an Interface (this may not be the best example, but hopefully it will be a good start).
The Location interface promises that an implementing class will have a single getAddress() method.
We'll also add another class, which represents a House.
Now at this point, Person and House are two very different classes....but...we could also say that they both have a Location. Yes, you can make an attribute for both of the Objects, but you could never make a list of Objects with both Person and Safe and yet still call a geAddress method....unless you have them implement an Interface.
So, if we have both of the Classes implement Location, both of the Classes will now *have* to have a getAddress() method. I'll show the changes in House below.
Notice I added a location field, with getters and setters, but I still have to have a getAddress() method that returns the location field. Once I finish House, I do the same thing to Person, but note I don't have to have the same name of the field if I don't want to, all I need to have is a getAddress() method.
Also note the @Override attribute. This lets the compiler know think this method overrides a superclass or interface.
I'm going to have to knock Netbeans on this area though. When you add a new field, you'll need to delete the equals(), hashCode() and toString() methods that are already defined in order to auto-generate them with the new field included. In
Ah, but now we have Person that implement Location, and House that implements Location. They both have an address. So now, we can do something like this
ArrayList<Location> items = new ArrayList<Location>();
Person p1 = new Person(); p1.setAddress("Building 1"); Person p2 = new Person(); p2.setAddress("Building 2"); House h1 = new House(); h1.setAddress("Building 1"); items.add(p1); items.add(p2); items.add(h1); for (Location loc : items) { System.out.println("Location is " + loc.getAddress(); }
Take your time to look at this code carefully.
The main thing here is that an Interface is a promise to implement a method, so that your code can treat the Object "almost" like another Object, which is the Interface. It isn't true multiple inheritance, but it comes close.
This is a very simple example of an Interface, but it shows some of the main traits. It turns out that in Java 8, Interface gain a bit more functionality, and you can actually define Default methods that have a full implementation...which in turn leads to true multiple inheritance (and its associated problems).
Packages
While not truly Object Oriented, packages still allow a certain grouping of Objects, and the management of the Object names. Packages allow you to qualify a name so that the name can be re-used in another package.
For example, if you look in the Java API, you can see that there are two Date classes in Java. The first is java.util.Date and the second is java.sql.Date. In fact, the java.sql.Date inherits from java.util.Date. Without packages, we couldn't have two Date classes, and sometimes an Object name, used in a package is the exact correct name for the Object, but there is another named Object with the same name that also makes sense for another package.
While it is true you don't *have* to use a package in an application, it is very strongly recommended, and always be required for our class.