Object Orientation - Core Concepts (in Java)
Object-oriented programming is an exceptionally bad idea which could only have originated in California.
- Overview
- Black Box
- Files
- Dogs
- Creating Difference
- Controlling Belongingness
- Comparing Sameness
- Stringification
- The 4 Pillars of Object-Orientation
- Alternative Paradigms
- Conclusions
Overview
Concept
Object-Oriented Programming (OOP), first experimented with in the 1950’s, is today the standard programming paradigm. Nearly all major contemporary programming languages offer the ability to program with objects.
-
code is written to represent virtual things.
-
each thing has certain properties (i.e. variables) that belong to it.
-
each thing has certain actions (i.e. methods) that it can perform.
Concept (continued)
With object-oriented programming, a developer writes a description of things of a certain type.
-
This acts as a fixed plan or concept for things of this type - a class.
-
Things are then created that embody the abstract concept - objects, also known as instances.
Digression
In the 5th century, B.C., the astonishingly brilliant philsopher, Plato, described what we now call the Theory of Forms.
-
In this theory, forms are the non-physical essences of all things, of which objects in the physical world are merely imitations or stand-ins.
-
Things, transient as they are, are not as real or true as the eternal concepts or blueprints from whence they come.
-
So, for example, any given tree is not as real as the concept of Tree.
-
But we cannot encounter forms directly, only through the objects that embody them, however imperfectly.
-
It took us only 2.5 thousand years to begin to code in that direction.
It’s imperative to understand this!
Object-oriented programming is a variety of imperative programming.
-
With imperative programming, we instruct the computer how to solve the tasks.
-
Programs make heavy use of memory and control flow: loops, conditional statements with branching, and so on, to establish the sequence of steps necessary to solve the problem.
-
An alternative to imperative programming would be declarative programming, where a desired end goal is declared, but the implementation details of how to achieve it are not. SQL is an example of a declarative language.
Black Box Metaphor
Smoke and mirrors
Despite being a form of imperative-style programming, specifying in detail how problems are to be solved, object-orientation nevertheless attempts to put to practice the black box metaphor of engineering, where, provided particular inputs, a machine produces a predictable output and the user of it doesn’t have to know the implementation details, even though they are written in the code.
Digression
The black box approach transcends any one discipline. For example, it was popular in the Behaviorist school of psychology, which held that human psychology could be defined by the empirically observed responses to given stimuli, regardless of the internal psychological thought patterns internal to the person.
-
It modeled humans as input-output machines…. black boxes where it was not necessary to understand the internal psychology of the person.
-
A pioneer of this movement was B.F. Skinner, who purportedly raised his own children in literal black boxes!
Digression (continued)
The “Skinner Box”:
Example 1 - Files
Concept
Imagine you wanted to write code that represented files on a computer’s hard drive.
Properties
Every file on the hard drive might have some properties, e.g.
- the data stored within the file
- metadata: filename, size, date modified, etc.
The values these properties hold collectively represent the internal state of any given file at any given moment in time.
Actions…
If we say a file is in control of its own destiny (a big if), we could say a file has a few actions it could take, e.g.:
- update the data it holds
- change its filename
- save itself to a particular location on the hard drive
… and their consequences
These actions represent a public interface through which other code can interact with any given file and instruct it what actions to take.
// telling the thing what to do...
myFile.setFilename("foobar.txt");
myFile.storeData(someData);
Calling these methods automatically updates the internal state of the thing.
// ...causes an internal update of its state - i.e. the values of the properties
filename = "foobar.txt";
data = someData;
UML class diagram
There is a standardized way of representing such things, called the Unified Modeling Language (UML). Here is an example of a UML ‘class’ diagram:
Class definition
This diagram can be translated into code as such:
public class File {
// properties
private byte[] data;
private String filename;
private int size;
private int modifiedDate;
// actions
public byte[] getData() { ... };
public void setData(byte[] data) { ... };
public String getFilename() { ... };
public void setFilename(String filename) { ... };
public void saveTo(String filePath) { ... };
}
Intention
A class definition is a template from which as many objects can be made as we like.
-
Each specific File object has its own copy of the properties and methods defined in the File class - these are termed instance properties ** and **instance methods.
-
Each specific File object can have its own specific set of values for the properties defined in the class.
-
Each specific File object will respond to calls to it using any of the methods defined in the class.
-
The internal state of each object is hidden by making the properties private. Code written inside other class definitions cannot see them.
Example 2 - Dogs
Concept
Imagine dog, the concept.
Properties
Every dog might have some properties, e.g.
- name
- age
- breed
- weight
Different dogs will probably have different values for each of these properties.
- The value of each dog’s properties at any given moment in time repesents that dog’s internal state.
Actions…
If we say a dog is in control of its own destiny (a big if), we could say a dog has a few actions it could take, e.g.:
-
bark
-
fetch
-
sleep
… and their complications
Two dogs with different internal states might implement these same actions differently.
-
For example, a lightweight lap dog might have a rapid fire tinny yapping sort of bark, whereas a heavy shepherd might occasionally produce a mellow deep woof sound.
-
A young dog might generally successfully fetch an object, while an elderly dog might generally fail at this.
-
A breed known for its abilities as a guard dog might bark more often and more fiercely than others.
UML class diagram
A UML class diagram for this dog thing:
Class definition
This diagram can be translated into code as such:
public class Dog {
// properties
private String name;
private int age;
private String breed;
private int weight;
// actions
public void bark() { ... };
public void fetch() { ... };
public void sleep() { ... };
}
Intention
This Dog class definition is a template from which as many Dog objects can be made as we like.
-
Each specific Dog object has its own specific values for each of the properties the class defines.
-
Each specific Dog object will respond to calls to any of the methods that the File class defines.
-
The actions produced by these method calls may differ slightly, depending upon the internal state of each Dog object
Difference
Constructing different objects from the same class
Our intention in creating a class is to be able to instantiate multiple distinct objects from that class template.
- We typically want these objects to have some differences from one-another.
No difference
The following code, placed in the code of some other class definition, would instantiate two Dog objects from our Dog class.
Dog dog1 = new Dog();
Dog dog2 = new Dog();
These are different things in memory. See for yourself.
(dog1 == dog2) // false... they are different references
But they do not have any difference in terms of their internal states.
String name
->null
by defaultint age
->0
by defaultString breed
->null
by defaultint weight
->0
by default
Difference!
If the internal properties of each Dog object were public or protected (which they are not), it might be possible to give each object distinct values:
Dog dog1 = new Dog();
// the following won't work, since the Dog's properties are all private
dog1.name = "Fido";
dog1.breed = "Bugle";
dog1.age = 10;
Dog dog2 = new Dog();
// the following won't work, since the Dog's properties are all private
dog2.name = "Tobik";
dog2.breed = "German Shepherd";
dog2.age = 3;
But this requires us to make visible the internal properties of the object, which goes against the black box metaphor and imperative-style programming.
Difference! (continued)
If we were able to set each Dog’s name, breed, and age to different internal states, while retaining hidden internal properties for each object, then we would achieve our goal.
We would like to be able to do something like this:
Dog dog1 = new Dog("Fido", "Bugle", 10);
Dog dog2 = new Dog("Tobik", "German Shepherd", 3);
At the moment, our class definition provides no mechanism for setting a given object’s internal properties.
Constructors
In order to be able to set each object’s properties discretely, we need to modify the Dog class to have a special constructor function.
public class Dog {
// a constructor function!
public Dog(String name, String breed, int age) {
// set this object's internal properties
this.name = name;
this.breed = breed;
this.age = age;
}
// properties
private String name;
private int age;
private String breed;
// and so on...
//...
Constructors (continued)
Note that constructor functions do not specify a return type.
public class Dog {
//...
public Dog(String name, String breed, int age) {
//...
}
//...
}
This, not that
The this
keyword is used to indicate which object we are talking about it.
-
Since a single class is a blueprint that can be used to create many objects, Java needs to know which object we are referring to.
-
So
this
always automatically refers to the object upon whom the current method is being called.
Imagine we defined the Dog
class’s bark method:
public class Dog {
//...
public void bark() {
System.out.printf( "%s says, 'Woof!' ", this.name );
}
//...
- Which dog’s name will be output when this method is called?
This, not that (continued)
The answer is, “whichever dog the method is called upon”.
If somewhere we call that method on a dog named Fido, then Fido’s name will be output.
Dog dog1 = new Dog("Fido", "Bugle", 10);
//...
dog1.bark(); // outputs "Fido says, 'Woof!' "
Whereas, if we were to call that method on a dog named Tobik, then Tobik’s name would be output.
Dog dog2 = new Dog("Tobik", "German Shepherd", 3);
//...
dog2.bark(); // outputs "Tobik says, 'Woof!' "
Instantiating objects
With constructor functions, we can instantiate as many objects as we like with whatever property values we want them to have.
Dog dog1 = new Dog("Fido", "Bugle", 10);
Dog dog2 = new Dog("Tobik", "German Shepherd", 3);
Instantiating objects (continued)
We could make 101 Dalmatians with random names and ages, if we wanted…
// let's make a random mix of names and ages objects
String[] names = "Patch,Lucky,Cadpig,Roly Poly,Penny,Freckles,Pepper".split(",");
int[] ages = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 };
Dog[] dogs = new Dog[101]; // will hold 101 Dalmatians
for (int i=0; i<dogs.length; i++) {
// assign each dog random property values from these arrays
String randomName = names[ (int) (Math.random() * names.length) ];
int randomAge = ages[ (int) (Math.random() * ages.length) ];
dogs[i] = new Dog( randomName, "Dalmatian", randomAge ); // instantiate a Dog object
dogs[i].bark(); // make each dog bark
}
Changing internal state
Constructors allow us to set up an object’s internal state at the moment of its creation. But what if we wanted to update those values at some later date?
-
For example, what if one of the 101 Dalmatians has a [happy] birthday and is now a year older… can we not update the age property?
-
In other words, we would like to be able to do something like this:
// instantiate an object with an initial internal statee
Dog dog1 = new Dog("Fido", "Bugle", 10);
// do some very important significant stuff...
// update the object's internal state at some later point
dog1.age = dog1.age + 1; // happy birthday!
By now, you know that this is not possible. The internal properties are private and cannot be accessed from ‘user’ code in a different class.
Setters
To change an object’s internal state, we need a ‘setter’ function to allow us to set the value of an existing object.
public class Dog {
// ...etc etc
// a setter function
public void setAge(int age) {
// first validate the value, and then use it if good
if (age > 0 && age < 15) this.age = age;
}
// ...and so on and so forth
// ...
-
Note that the setter method performs validation - it only allows good values to be placed into the property - this is typical of setters.
-
Note also that the method is public, so it can be called from code outside the
Dog
class.
Setters (continued)
With setters, it is possible to set the values of properties on existing objects.
Assuming we have created a Dog object…
Dog dog1 = new Dog("Roly Poly", "Dalmatian", 10); // instantiate a dog
…we can now update its age using the setter
dog1.setAge(11);
Setters (continued again)
If setters are present, they should always be used whenever an object’s property values need to be changed.
-
if a constructor sets an object’s initial starting property values, it should do so by calling the object’s setters.
-
if any other method adjusts an object’s property values, it should do so by calling the object’s setters.
-
this guarantees that any validation performed within the setter function is always used, and this validation logic is written in only one place in the code - in the setter.
Setters (continued once again)
For example, our original constructor can be updated to use setters that validate the values being set:
public Dog(String name, String breed, int age) {
this.setName(name);
this.setBreed(breed);
this.setAge(age);
}
Accessing internal state
What if you wanted to know a particular Dog’s age… how could you find that out programmatically?
-
For example, instead of hard-coding
11
in the statementdog1.setAge(11);
, could we have programmatically determineddog1
’s current age and simply added one to that? -
We certainly cannot do the following, since the age property is private and cannot be read from code outside the
Dog
class:
dog1.setAge ( dog1.age + 1 )
Getters
Of course, we can allow access to an object’s internal state. But we need to create ‘getter’ functions to do so.
public class Dog {
// ...etc etc
// a getter function
public int getAge() {
// simply return the value of the age property
return this.age;
}
// ...and so on and so forth
// ...
-
Note that this function simply returns the value of a private property of the object. It may seem a bit redundant, but this is typical of getters.
-
Note also that the method is public, so it can be called from code outside the
Dog
class.
Getters (continued)
With a getter function, we can read the current value of a property of an object…
System.out.println ( dog1.getAge() ); // will output dog1's age
… and use that information to help us understand that object’s internal state
dog1.setAge( dog1.getAge() + 1 );
Knowledge of an object’s internal state can help us make decisions
if ( dog1.getBreed().equals("Dalmation") && dog1.getName().equals("Pepper") ) {
// use your imagination
}
Controlling Belongingness
Non-static properties and methods
Neither setters nor getters, nor even the methods that represent actions of the object (e.g. bark, fetch, sleep) were declared with the keyword static
in their method signatures. Neither were any of the properties (e.g. name, breed, age).
-
The keyword,
static
, is used to indicate that a method or property belongs to the class as a whole, not to each particular object independently. -
Thus, properties and methods that we want to belong to each object individually do not use the
static
keyword. -
Each dog has its own name, breed, age, and each dog does its own barking, fetching, and sleeping. So it makes sense that these properties and methods would not be static.
-
Making such properties and methods static would indicate that all dogs share them - one name for all, one breed for all, one age for all, all fetching as one, sleeping as one, eating as one, etc.
Shared concerns
There are, of course, cases where it makes sense for a property or method to belong to the class as a whole… i.e. for it to be declared as static
.
-
For example, imagine that you wanted to keep track of how many dog objects had been instantiated from the Dog class.
-
You could create a
static
property namednumDogs
that is incremented each time a Dog is instantiated.
Shared concerns (continued)
public class Dog {
//...
public Dog(String name, String breed, int age) {
//...
Dog.numDogs++; // increment the static property
//...
}
//...
private static int numDogs = 0;
//...
-
Note that we refer to the property as
Dog.numDogs
, rather thanthis.numDogs
. -
this.numDogs
would suggest that thenumDogs
property belongs only to the Dog being instantiated, which is not correct - an error! -
Dog.numDogs
clearly communicates that thenumDogs
property belongs to theDog
class as a whole - correct!
Shared concerns (continued again)
Every time we instantiate a new Dog
object, the counter will be incremented.
Dog dog1 = new Dog("Fido", "Bugle", 10);
Dog dog2 = new Dog("Tobik", "German Shepherd", 3);
-
Because the counter was declared as
private
, we cannot see how many dogs have been made from code outside theDog
class. -
if were to make a
public
getter method for thenumDogs
property defined within theDog
class, we would be able to call it from outside theDog
class to find out how many dogs had been made.
System.out.printf( "There exist %d dogs in our world.\n", Dog.getNumDogs() );
Comparing Sameness
Different kinds of difference
There are a few ways we can compare two objects.
-
by whether they are, in fact, the same object in memory
-
by whether their internal states are the same or similar
Identity comparison
To check whether two supposedly-separate objects are, in fact, the same object, use the ==
operator.
if (dog1 == dog2) {
System.out.printf("%s is actually the same dog as %s!\n", dog1.getName(), dog2.getName);
}
-
Objects in Java are reference types, meaning that a variable assigned to point to an object holds the memory address of that object.
-
The
==
operator, when used with a reference type, thus compares memory addresses.
Objects as reference types
Because objects are reference types, when they are passed as arguments to a method, the memory address is what is passed.
- For example, imagine a method that changes any dog’s name to “Dog”…
public void rename(Dog d) {
d.setName("Dog");
}
- … and let’s say we call that method from some other method.
Dog dog1 = new Dog("Fido", "Bugle", 10);
rename(dog1);
System.out.println ( dog1.getName() )
-
The dog object referred to within the
rename
method asd
is the same dog object referred to by the calling method asdog1
- they are aliases. -
So the printed output will be “Dog”, not “Fido”.
Internal state comparison
To check whether two objects share the same internal state, even if they are different objects in memory, we have to roll our own solution.
- Conventionally, this would be done by defining a method named
equals
that performs the comparison.
public class Dog {
//...
public boolean equals(Dog that) {
boolean sameName = this.name.equals( that.getName() ); // are the names the same?
boolean sameAge = ( this.age == that.getAge() ); // same age?
boolean sameBreed = ( this.breed.equals( that.getBreed() ) );
return sameName && sameAge && sameBreed; // true if all the same, false otherwise
}
//...
Internal state comparison (continued)
To compare two dogs, you would use one of those dogs’ equals()
method.
if ( dog1.equals(dog2) ) {
System.out.printf("%s and %s have the same internal state!\n", dog1.getName(), dog2.getName);
}
EqualsBuilder
Since object’s properties are often of many different types, writing a custom [].equals()](#tedium) method must be done carefully and tediously. Fortunately, Apache Commons Lang’s EqualsBuilder
class can make object comparisons easier and less prone to silly errors.
public class Dog {
//...
public boolean equals(Dog that) {
return new EqualsBuilder()
.append(this.name, that.name)
.append(this.age, that.age)
.append(this.breed, that.breed)
.append(this.weight, that.weight)
.isEquals();
}
//...
- To use
EqualsBuilder
, Commons Lang’s main.jar
file must be downloaded and added as a project dependency, for example by placing it within a project’slib
directory. - The library must then be imported with
import org.apache.commons.lang3.SystemUtils;
.
Stringification
Concept
Converting an object to a String usually leads to unwanted text.
// instantiate a Dog object
Dog dog2 = new Dog("Tobik", "German Shepherd", 3);
// do something that requires the object to be converted to a String
String text = String.format("The dog as a string looks like: %s", dog2);
// see what you get...
System.out.println(text);
By default, the output would show the class name of the object and a hashcode - random-looking text that is not probably what you hoped for. Try it!
The dog as a string looks like: Dog@63961c42
Wouldn’t it be nice if we could instead output something descriptive, like,
The dog as a string looks like: Tobik, a 3-year-old German Shepherd
Fulfilling our desires
Java allows an object to describe how it should be converted to a String with a special method named toString()
that returns the String equivalent of the object.
public class Dog {
//...
public String toString() {
String myself = String.format( "%s, a %d-year-old %s", this.getName(), this.getAge(), this.getBreed() );
return myself;
}
//...
}
Now, converting the object to a String will result in something more descriptive, such as:
Tobik, a 3-year-old German Shepherd
The 4 Pillars of Object-Orientation
Concept
No university course would pass the censors unless it mentioned at least one set of immutable laws students must memorize despite their better judgment.
- Our course is no different. So here they are.
The four pillars of object-oriented programming:
-
Abstraction
-
Encapsulation
-
Inheritance
-
Polymorphism
Abstraction
abstract (adjective): disassociated from any specific instance
Abstraction is thus the process of making something abstract or the state of being abstracted.
-
The black box metaphor encourages abstraction.
-
Hiding implementation details of code encourages abstration
-
Classes are abstractions of the objects that are instantiated from them.
-
Static properties and methods are abstractions, as they are not tied to any instance.
Encapsulation
encapsulate (verb): to enclose in or as if in a capsule
Encapsulation is thus the process of enclosing some things within other things.
-
Instance properties and methods are encapsulated within the objects to which they belong.
-
Static properties and methods are encapsulated within the class to which they belong.
-
Classes are encapsulated within the packages to which they belong.
-
Packages may be encapsulated within parent packages.
Inheritance
inherit (verb): to receive from an ancestor
Inheritance is thus that which is inherited from an ancestor.
-
Classes, and the objects instantiated from them, can inherit properties and methods from ancestor classes (we have not discussed this yet).
-
Classes, and the objects instantiated from them, can inherit properties and methods from ancestor interfaces (we have not discussed this yet).
Polymorphism
polymorphism (noun): the quality or state of existing in or assuming different forms
-
Objects can be instances of more than one class (we have not discussed this yet).
-
Objects can also be instances of one or more interfaces (we have not discussed this yet).
Alternative Paradigms
Free choice
There is currently no major threat to object-oriented programming, since it is so widespread. However, other programming paradigms do exist:
-
Functional programming is focused on data flow (i.e. immutable values being used in formulae to compute other values) , rather than control flow (i.e. “Do this, then do that!” and where variables are not values but rather memory locations with changing values over time). Popular languages for functional programming are Excel and Haskell. There is growing interest in this 70-year-old style! Learn more!
-
Procedural programming was the standard paradigm before object-oriented programming took over. It is still what is taught 70 years later in many entry-level programming courses, where the instructors are perhaps afraid to talk (or think) abstractly. The problem to be solved by a program is decomposed into sub-problems, and procedures (a.k.a. functions) to solve each problem are written and executed in sequence.
The future
- Many see a multi-paradigm future, and some see a multi-paradigm present, where paradigms are mixed and matched, and each is used for its unique strengths.
Conclusions
You now have a basic understanding of object-oriented programming in Java. Well done.
- Thank you. Bye.