Object-oriented programming has gone from an obscure research topic (in the 1970's) to taking over the mainstream (1990's).
Why? What good are objects?
First, let's clarify what we mean by Object-Oriented Programming:
Next, let's look at Java/OOP features one by one.
What is encapsulation? Answer: Packaging related data and code into a
single name-able unit.
Example:
=> reduces name clashes.
class A {
static int i;
static void print ()
{
System.out.println ("my age is" + i);
}
}
class B {
static int i;
static void print ()
{
System.out.println ("my weight is = " + i);
}
}
public class Test {
public static void main (String[] argv)
{
// Use the print method in A:
A.print ();
// Use the print method in B:
B.print ();
// No name clash for the print methods (or data).
// Typically, A and B will be written by different programmers.
// => they won't need to worry about name clashes.
}
}
It's important to understand the distinction between static and
dynamic classes:
Another example:
class A {
static int i;
static void print ()
{
System.out.println ("my age is" + i);
}
}
public class Test {
public static void main (String[] argv)
{
// Use the print method in A by naming the class.
A.print ();
}
}
class B {
// NOTE: we've left out the reserved word "static"
int i;
void print ()
{
System.out.println ("my age is" + i);
}
}
public class Test {
public static void main (String[] argv)
{
// We need to create an INSTANCE first:
B b = new B ();
// Note: the instance is pointed to by the variable "b".
// Now access members via the instance variable:
b.print ();
// Create another instance in the variable "b2".
B b2 = new B ();
b2.print ();
}
}
=> a blob of memory assigned at the time of creation.
class A {
// Static data and methods:
static int data1 = 1;
static void methodOne ()
{
System.out.println ("method one: data1=" + data1);
}
// Dynamic data and methods:
int data2 = 2;
void methodTwo ()
{
System.out.println ("method two: data2=" + data2);
}
}
public class TestClasses {
public static void main (String[] argv)
{
// Call the static method directly using the class name,
// the dot-operator and the method name.
A.methodOne ();
// Access static data similarly:
System.out.println (A.data1); // Prints "1".
// Note: Java uses the term "class method" to refer to a static method.
// The term "class method" is just their jargon, and is not a reserved word.
// For a dynamic method, we need to first create an instance and
// then call the method using the instance variable.
A a = new A ();
a.methodTwo ();
// Access data similarly:
System.out.println (a.data2); // Prints "2".
// Create another instance:
A x = new A ();
x.data2 = 3;
x.methodTwo (); // Prints "3".
a.methodTwo (); // Prints "2" (Variable "a" was not affected).
// Note: the variable "a" is really a pointer that points to a blob
// of memory on the heap. This blob contains space for data like "data2".
// The blob assigned to variable "x" is different and has its own space
// allocated for dynamic data and methods.
// It also helps to think that it has space for methods like "methodTwo()".
// In an actual implementation, only a pointer to the method is stored
// in the blob, so that the real code is elsewhere (pointed to, by the pointer).
// Java jargon: the term "instance variables" is used for dynamic variables,
// just as they use "instance methods" instead of "dynamic methods".
}
}
What is inheritance?
Why use it?
Example:
class A {
// Instance data and method:
int data2 = 2;
void methodTwo ()
{
System.out.println ("method two: data2=" + data2);
}
}
class FancyDataStructure {
// This data structure has been written for objects of type "A".
// The code could be long and tedious, but it's written just once,
// compiled and handed out to others for use.
static void insertStuff (A a)
{
// This method expects an "A" type object coming in.
// ... The actual code is not relevant to the example ...
}
}
class B extends A {
// B's own stuff:
void methodThree ()
{
// ... not relevant ...
}
}
public class TestClasses3 {
public static void main (String[] argv)
{
A a = new A ();
FancyDataStructure.insertStuff (a);
// Here, the method has been written, and let's say, compiled
// for "A" type objects. But, later we want to add "B" type
// objects as well. We can do that directly, as the method call above shows,
// without having to re-write the data structure for "B" type objects.
// We can do this because every B object is an A object. Indeed,
// the data structure can be compiled and written just ONCE for
// A type objects.
// OK, so let's add a "B" object to the data structure:
B b = new B ();
FancyDataStructure.insertStuff (b);
// Note that as far as FancyDataStructure is concerned, we inserted
// an "A" object into it. This is called polymorphism.
// A "B" object is a "morph" an "A" object.
// Now, there's another important use of overriding, as the
// next example shows.
}
}
Let's look at an example. The comments explain ...
class A {
int methodNeededInDataStructure ()
{
// ... whatever ...
return 1;
}
}
class FancyDataStructure {
// This data structure has been written for objects of type "A".
static void insertStuff (A a)
{
// This method expects an "A" type object coming in
// and is going to call the "methodNeededInDataStucture()", as in:
int x = a.methodNeededInDataStructure();
// ... The actual code is not relevant to the example ...
}
}
class B extends A {
// B's own stuff:
void methodThree ()
{
// ... not relevant ...
}
// B overriddes the method needed for the data structure.
int methodNeededInDataStructure ()
{
// ... whatever ...
return 2;
}
}
public class TestClasses4 {
public static void main (String[] argv)
{
A a = new A ();
FancyDataStructure.insertStuff (a);
B b = new B ();
FancyDataStructure.insertStuff (b);
// Note that as far as FancyDataStructure is concerned, we inserted
// an "A" object into it. However, it is really a "B" object that
// has its own implementation of the "methodNeeded ..." method that
// the data structure called.
}
}
class A {
int compare (A a)
{
// ... An "A" object knows how to make a comparison with
// another "A" object.
}
}
class FancyDataStructure {
// This data structure has been written for objects of type "A".
static void insertStuff (A a)
{
// This method expects an "A" type object coming in.
// Some where in here, there will be a need to make comparisons like
int compValue = a.compare (x);
if (compValue < 0) {
// ... yada, yada ...
}
// Here, x is some existing "A" object in the data structure.
}
}
class B extends A {
// B overriddes the method needed for the data structure.
// NOTE: it must have the same signature to override, and so must
// declare an "A" object as parameter.
int compare (A a)
{
// It knows that the input parameter will be a "B" object
// because it expects the user of "B" objects to store
// a bunch of "B" objects in the data structure.
B b = (B) a;
// Now make comparison that is relevant to "B" objects....
// Of course, a careless user of "B" objects can store both "A" and
// "B" objects, in which case this will be called with an
// honest-to-goodness "A" object. This will cause a casting exception.
// A slick programmer will anticipate this and have code that
// uses the "instanceof" operator like this ...
if (! (a instanceof B)) {
// ... take care of the problem ...
}
}
}
public class TestClasses5 {
public static void main (String[] argv)
{
// Insert one object ...
B b = new B ();
FancyDataStructure.insertStuff (b);
// Then, another ...
b = new B ();
FancyDataStructure.insertStuff (b);
}
}
Abstract classes help force an implementation:
abstract class Stuff {
abstract int compare (Stuff x);
}
class A extends Stuff {
int compare (Stuff x)
{
A a = (A) x;
// ... compare etc ...
}
}
class FancyDataStructure {
// This data structure has been written for objects of type "A".
static void insertStuff (Stuff x)
{
// This method expects an "Stuff" type object coming in.
// Some where in here, there will be a need to make comparisons like
int compValue = x.compare (y);
if (compValue < 0) {
// ... yada, yada ...
}
// Here, y is some existing "Stuff" object in the data structure.
}
}
public class TestClasses6 {
public static void main (String[] argv)
{
// Insert one object ...
A a = new A ();
FancyDataStructure.insertStuff (a);
a = new A ();
FancyDataStructure.insertStuff (a);
}
}
abstract class A {
// An abstract class can have non-abstract methods.
void methodOne ()
{
// ...
}
// If even one method is abstract, the class is abstract.
abstract void methodTwo ();
}
class B extends A {
// Inherits the non-abstract methods in A.
// Must implement the abstract method if you want to
// create instances of B.
void methodTwo ()
{
// ...
}
}
public class TestClasses7 {
public static void main (String[] argv)
{
B b = new B ();
// Call an inherited method.
b.methodOne ();
// Call an implemented method that was abstract.
b.methodTwo ();
}
}
abstract class A {
abstract void methodOne ();
}
abstract class B {
abstract void methodTwo ();
}
// Class C can only extend one other class. C cannot
// extend both A and B.
class C extends A {
void methodOne ()
{
// ...
}
}
public class TestClasses8 {
public static void main (String[] argv)
{
// ...
}
}
interface A {
abstract void methodOne ();
}
interface B {
abstract void methodTwo ();
}
// Class C can only extend one other class, but
// can implement many interfaces.
class C implements A, B {
public void methodOne ()
{
// ...
}
public void methodTwo ()
{
// ...
}
}
public class TestClasses9 {
public static void main (String[] argv)
{
C c = new C ();
c.methodOne ();
c.methodTwo ();
}
}
interface A {
abstract void methodOne ();
}
interface B {
abstract void methodTwo ();
}
class D {
void methodThree ()
{
// ...
}
}
// Class C can extend one class and implement as many interfaces
// as desired.
class C extends D implements A, B {
public void methodOne ()
{
// ...
}
public void methodTwo ()
{
// ...
}
}
public class TestClasses10 {
public static void main (String[] argv)
{
C c = new C ();
c.methodOne ();
c.methodTwo ();
c.methodThree ();
}
}
To summarize, objects are useful because: