Java Object-Orientation

This Object-Oriented programming page includes the following

This web page does not dive deep into OO concepts (there are numerous books on the subject that are far better explaining the OO concepts than me) but outlines the basic OO concepts and any rules that you need to know, again this is intended to be a quick guide.

OOP encapsulates data (attributes) and methods (behaviors) into objects, the data and methods are intimately tied together, implementation details are hidden within the objects themselves. In Java the unit of programming is the class from which objects are eventually instantiated. Methods and data are encapsulated within the walls of the class. A class contains the data and the methods that access the data contained within the class. The data contained in a class is called the instance variables.

Encapsulation

The ability to make changes in your implementation code without breaking the code of others who use your code is a key benefit of encapsulation. You want to hide (mask) implementation details behind a public programming interface. By interface we mean the set of accessible methods your code makes available for other code to call, in other words, your codes API's. You can change the code without forcing a change in the code that calls your changed methods.

To provide maintainability, flexibility and extensibility you must encapsulate the code and to do this you

Set and Get methods

public class Football {
    private String team;                  // this is hidden to the outside world
    private int groundCapacity;           // this is hidden to the outside world

   public void setTeam(String t) { ... }
   public String getTeam() { ... }

   public void setGroundCapacity(int c) { ... }
   public int getGroundCapacity { ... }
}

Note: the only way to get to the instance variables is via the set and get methods

When you create a new class that does something you will document only what access methods (get and set) are available to the public and this will not change, whoever implements your class can get updates and improved versions knowing that implementing this newer version will not break his/her program as the return types and passing variables to methods will be the same.

Inheritance

There are a few rules regarding IS-A and HAS-A relationships

IS-A
(inheritance)

In OO, the concept of IS-A is based on inheritance, IS-A is a way of saying "this thing is a type of that thing", for example a dog is a type of animal so a dog IS-A animal, bentley IS-A car, apple IS-A fruit, etc

This is expressed in Java through the keyword extends
   public class Vehicle { ... }
   public class Car extends Vehicle { ... }
   public class Ka extends Car { ... }

So we can say that "Ka IS-A car" and "Ka IS-A Vehicle" because a class is said to be "a type of" anything further up in its inheritance tree.

When using the extends keyword a class inherits all accessible member variables and methods

public class TestAnimal {
   public void static main (String[ ] args) {
      Horse h = new Horse();

      h.getEat();                              // getEat() method inherited from Animal
      h.food = "Hay and Oats";           // change the inherited (from Animal) food variable

     System.out.println("I like to eat " + h.food);
}

class Animal {
   public String food = "Green Stuff";

   public void getEat() {
        System.out.println("General food from the Animal class");
   }
}

class Horse extends Animal {
    // Will inherit Animals getEat() method and food member variable
}

HAS-A
(usage)

HAS-A relationships are based on usage, rather than inheritance, in other words class A HAS-A B if code in class A has a reference to an instance of class B for example "city HAS-A road".

The below Horse class has an instance variable of type Halter, so you can say that "Horse HAS-A Halter" (Horse has a reference to Halter). This means that you can invoke any of the methods in Halter, this is good OO practice as we do not need to duplicate the methods in Halter and this means that any other class can use Halter methods thus reducing code.

public class Animal { ... }
public class Horse extends Animal {
   private Halter myHalter;

   public void tie(LeadRope rope) {
      myHalter.tie(rope);              // delegate tie behavior to Halter Object
   }
}

Users of the Horse class think that the Horse class has Halter behavior, the Horse class may have a method called tieRope but this method could delegate this to the Halter tieRope method (as seen above), users of Horse class should never know that there is a Halter, only that is capable of things you ask it to do.

There are visual object modeling languages such has the Unified Modeling Language (UML) which allow designers to design and easily modify classes without having to write code first because object-oriented components are represented graphically. This helps coders to create a map of the class relationships and helps them recognize errors before coding begins. There are many design patterns available which cover a lot of different types of projects.

To obtain what class an object belongs to you can use the following

what class an object belongs to System.out.println("The class of object " + obj + " is " + obj.getClass().getName() );

Overridden Methods

Methods can be overridden and overloaded, constructors can only be overloaded. Overload methods and constructors let you use the same name but different argument lists, overriding lets you redefine a method in a sub-class when you need new sub-class behavior unless the method is mark final. In the case of a abstract method you have no choice you must override it.

The rules for overriding are as follows

The key benefit of overriding a method is to define behavior that is specific to the class

Overriding example

public class Animal {                            // This will be the super class
   public void eat() {
      System.out.println("Generic Animal food");

   public void printAnimal {
      System.out.println("This is the Animal class");
   }
}

class Horse extends Animal {                     // Horse is a sub-class of Animal
   public void eat() {                           // override the Animal eat method
      System.out.println("I like oats, hay");

   public void printAnimal {
      super.printAnimal();                // Invoke the Animal super class code, then come back here
   }
}

class Lion extends Animal {                      // Lion is a sub-class of Animal
   public void eat() {                           // override the Animal eat method
      System.out.println("I like to eat Horses ");
}

Note: to call methods from the superclass use the super keyword.

OverLoading Methods

Overloaded methods lets you reuse the same method name in a class, but with different arguments (and optionally a different return type), you help out other programmers by supplying different argument types so they don't have to perform conversions before invoke your method.

The rules for overloading are as follows

Overloaded Example // Original Method
public void changeSize(int size, String name, float pattern) { ... }

// Overloaded Methods
public void changeSize(int size, String name) { ... }
public void changeSize(float pattern, String name throws IOException) { ... }
public int changeSize(int size, float pattern) { ... }

Difference between Overloaded and Overriding

 
Overloaded
Overridden
Argument list
Must change Must not change
Return type
Can Change Must not change
Exceptions
Can Change Can reduce or eliminate Must not throw new or broader checked exception
Access
Can Change Must not make more restrictive (can be less restrictive)
Invocation
Reference type determines which overloaded version (based on declared argument types) is selected. Happens at compile time. The actual method that's invoked is still a virtual method invocation that happens at runtime, but the compiler will always know the signature of the method that is to be invoked. So at runtime, the argument match will have already been nailed down, just not the actual class in which the method lives Object type (in other words, the type of the actual instance on the heap) determines which method is selected Happens at runtime.

Polymorphism

The word polymorphism is Geek and means "many forms". In Java, polymorphism means using a superclass variable to refer to a subclass object. Polymorphism is useful in that it allows Java programs to be written more abstractly, and more abstraction allows for more efficiency and less redundancy. In general, polymorphism allows inheritance and interfaces to be used more abstractly.

Overloading and Overriding are basically two types of polymorphism, as will not know which method we will run until runtime.

The below example does not make the decision to make variable animal a certain type does not occur until runtime, this is called late binding (also known as dynamic binding) and it's late binding that allows polymorphism to work, this is the third type of polymorphism.

Polymorphism example

public class TestAnimals {
   public static void main (String [ ] args) {
      Animal a = new Animal();
      Animal b = new Horse();      // Animal is a reference type, Horse is a instance type
   
     a.eat();      // Run's the Animal version of eat();
     b.eat();      // Run's the Horse version of eat();

     b.buck();     // Unable to run this, as Animal reference does not have access to the buck method
                   // the compile looks only at the reference type not the instance type, so a Animal
                   // reference only see's the methods in the Animal class
   }
}

class Animal {
   public eat() {
       System.out.println("Generic Animal food");
   }
}

class Horse extends Animal {
   public void eat() {
      System.out.println("I like oats, hay");

   public void buck() {
      System.out.println("I like to buck-a-roo!");
   }
}

Polymorphism example
   (bit more advanced)
public class polyExample {

   public static void main(String[] args) {
      Employee ref;

      Boss b = new Boss("Paul", "Valle", 1000);
      HourlyWorker h = new HourlyWorker("Dominic", "Valle", 15, 40);

      ref = b;

      // Using polymorphism using the object referenced by ref
      System.out.println( ref.toString() + " earned £" + ref.earnings());

      // ref can only access methods if the method names are in Employee
      // for an example ref cannot access setWeeklySalary it is not in Employee

      // ref.setWeeklySalary(1500); // cannot perform

      // explicity invoke the Boss version
      System.out.println( b.toString() + " earned £" + b.earnings() + "\n");

      ref = h;

      // Using polymorphism using the object referenced by ref
      // ref can only access methods if the method names is in Employee
      // for an example ref cannot access setWeeklySalary it is not in Employee
      System.out.println( ref.toString() + " earned £" + ref.earnings());

      // explicity invoke the Boss version
      System.out.println( h.toString() + " earned £" + h.earnings());
   }
}

abstract class Employee {
   private String firstName;
   private String lastName;

   // Constructor
   Employee(String first, String last) {
      firstName = first;
      lastName = last;
   }

   // Return the first name
   public String getFirstName() { return firstName; }

   // Return the first name
   public String getLastName() { return lastName; }

   public String toString() { return firstName + ' ' + lastName; }

   // The abstract method
   public abstract double earnings();
}

class Boss extends Employee {
   private double weeklySalary;

   // Constructor for class Boss
   Boss(String first, String last, double s) {
      super(first, last);
      setWeeklySalary(s);
   }

   public void setWeeklySalary( double s) { weeklySalary = ( s > 0 ? s : 0 ); }

   // full fill the abstract contract by overriding the earnings method
   public double earnings() { return weeklySalary; }

   // override the Employee toString method
   public String toString() { return "Boss: " + super.toString(); }
}

class HourlyWorker extends Employee {
   private double wage;
   private double hours;

   // Constructor
   HourlyWorker( String first, String last, double w, double h) {
      super(first, last);
      setWage( w );
      setHours( h );
   }

   public void setWage( double w ) { wage = (w > 0 ? w : 0); }

   public void setHours( double h) { hours = (h >= 0 && h < 168 ? h : 0); }

   // full fill the abstract contract by overriding the earnings method
   public double earnings() { return wage * hours; }

   // override the Employee toString method
   public String toString() { return "HourlyWorker: " + super.toString(); }
}

Constructors and Instantiation

Objects are constructed, you cannot make a new object without invoking a constructor. Constructors are code that runs whenever you use the keyword new. Constructors cannot specify a return type or return values..

Every class even abstract classes must have a constructor, but just because a class must have one, doesn't mean the programmer has to type it, the Object class will provide one and as every object inheriates class Object. Interfaces do not have a constructor you cannot instantiate a interfaces.

Example Constructor

class Test {
   Test() { ... }                           // The constructor for the Test class
}

Note: there is no return type and the constructors name must match exactly the class name

Constructors are normally used to initialize instance variables.

Example Constructor

class Test {
   int size;
   String name;

   Test(String name, int size) {
      this.name = name;                     // the this keyword refers to the classes instance variables
      this.size = size
   }
}

Note: see below for more information on this

When you invoke a constructor using the new keyword you can create a constructor chain

Constructor Chaining

What happens when you create a new object in this case Horse extends Animal

Horse h = new Horse();

  1. Horse constructor is invoked
  2. Animal constructor is invoked (Animal is superclass of Horse)
  3. Object constructor in invoked (Object is the ultimate superclass of all classes)
  4. Object instance variables are given their explicit values (if any)
  5. Object constructor completes
  6. Animal instance variables are given their explicit values (if any)
  7. Animal constructor completes
  8. Horse instance variables are given their explicit values (if any)
  9. Horse constructor completes
4. Object()
3. Animal() calls super() - Objects constructor
2. Horse() calls super() - Animals constructor
1. Main() calls new Horse () - Horses constructor

Constructors have the following rules

The below tables details what the compiler will generate for your class regardless if you have typed it or not.

Class Code (What you type) Compiler-Generated Constructor Code (in Bold type)
class Foo { } class Foo {
   Foo() {
      super();
   }
}
class Foo {
   Foo() { ... }
}
class Foo {
   Foo() {
      super();
   }
}
public class Foo { } class Foo {
   public Foo() {
      super();
   }
}
class Foo {
   Foo(String s) { ... }
}
class Foo {
   Foo(String s) {
      super();
 
  }
}
class Foo {
   Foo(String s) {
      super();
   }
}
Nothing - compiler does not need to insert anything
class Foo {
   void Foo() { ... }
}
class Foo {
   void Foo() { ... }

   Foo(){
      super();
   }
}

Note: void Foo is a method because of the return type

You can overload constructors just the same as methods

Constructor overloading class Foo {
   Foo() { ... }
   Foo(String s) { ... }
   Foo(String s, int t) { ... }
}

Using the this Reference

When a method of a class references another member of that class for a specific object of that class, how does Java ensure that the proper object is referenced, the answer is that each object has access to a reference to itself called the this reference. The this reference is implicity used to refer to both instance variables and methods of an object.

this example public class thisTest {

   public static void main(String[] args) {

      myClass m = new myClass(17, 55, 59);
      mtClass m1;

      m.getTime();

      m1 = m.getObject;                  // return the m object using this
      m1.getTime();

      if ( m.equals(m1) )                // they should be the same object
         System.out.println("m and m1 are the same object");
   }
}

class myClass {

   private int hour, minute, second;     // access using this reference

   // Constructor
   myClass(int hour, int minute, int second) {
      this.hour = hour;                  // reference instance variable hour
      this.minute = minute;              // reference instance variable minute
      this.second = second;              // reference instance variable second
   }

   public void getTime() {
      System.out.println("Hour: " + hour + " Minute: " + minute + " Second: " + second);
   }

   public myClass getObject() {
      return this;                       // return this object
   }
}

Legal Return Types

You have different rules on returns types depending if you overriding or overloading

There are six further rules which effect both overriding and overloading

You can return null in a method that has an object reference return type public Button doStuff() {    // Button is the object reference return type
   return null;
}
An array is a perfectly legal return type

public String [] go() {
   return new String[] {"Fred", "Peter", "Jane"};
}

In a method with a primitive return type, you can return any value or variable that can be implicitly converted to the declared return type public int Foo() {
   char c = 'c';
   return c;          // char is compatible with int
}
In a method with a primitive return type, you can return any value or variable that can be explicitly cast to the declared return type public int Foo() {
   float f = 32.5f;
   return (int) f;
}
You must not return anything from a method with a void return type public void Foo() {
   return "this is it";      // Not legal
}
In a method with an object reference return type, you can return any object that can be implicitly cast to the declared return type

## Example One
public Animal getAnimal() {
   return new Horse();         // Assume Horse extends Animal, so OK to return
}

## Example Two
public Object getObject() {
   int[] nums = {1,2,3};
   return nums;                // Return an int array, which is still an object, so OK to return
}

## Example Three
public interface chewable { ... }
public class Gum implements Chewable { ... }

public class TestChewable {
   // Method with an interface return type
   public Chewable getChewable {
      return new Gum();                 // Return interface implementer, so OK to return
   }
}