5: OO ConceptsS2C Home « 5: OO Concepts

We start this lesson by looking at encapsulation, its advantages and how to achieve it within our code. We then look at the design principles of coupling and cohesion and describe the benefits of loose coupling and high cohesion. We then explain polymorphism and how we use it in the Java language before looking at object reference casting and determine when casting will be necessary whilst recognizing compiler vs. runtime errors, related to it. After this we examine the effect of modifiers on inheritance with respect to constructors, instance or static variables, and instance or static methods. We then look at code that declares and/or invokes overridden or overloaded methods and code that declares and/or invokes superclass or overloaded constructors. We finish the lesson by investigating code that implements "is-a" and/or "has-a" relationships.

Lets take a look at the points outlined at the Oracle Website for this part of the certification.

  • Section 5: OO Concepts

    1. Develop code that implements tight encapsulation, loose coupling, and high cohesion in classes, and describe the benefits.

    2. Given a scenario, develop code that demonstrates the use of polymorphism. Further, determine when casting will be necessary and recognize compiler vs. runtime errors related to object reference casting.

    3. Explain the effect of modifiers on inheritance with respect to constructors, instance or static variables, and instance or static methods.

    4. Given a scenario, develop code that declares and/or invokes overridden or overloaded methods and code that declares and/or invokes superclass or overloaded constructors.

    5. Develop code that implements "is-a" and/or "has-a" relationships.

EncapsulationTop

Encapsulation is a language mechanism used to restrict access to an object's components. In Java parlance this is a mechanism to restrict access to the members (instance variables and methods) of a class, constructors and even to the class itself. So how do we achieve this in Java?. Well Java provides us with the access modifier mechanism for restricting access to our classes, which are the basic unit of encapsulation in Java. We can also restrict access to our instance variables whilst providing access to them via public methods. We can limit construction to the class itself using the private keyword, or to the package the implementation is in using the protected keyword or with no modifier package-private / (the default). The table below lists access modifiers in more detail.

Access ModifiersTop

The table below shows the four types of access available in Java from the most open (public) to the most restrictive (private). We can only explicitly apply the public access modifier to our top-level classes (the classes we compile) but for members and constructors we can explicitly apply the protected and private access modifiers as well. We will talk about packaging in the Packages lesson, but for now we are going to examine how to protect our class members from unwanted access and modification.

Access modifier Class Member Constructor Description
publicYesYesYesA top-level class may be declared with the public access modifier, and if it is the class is accessible to all other classes everywhere.
A member may be declared with the public access modifier, and if it is the member is accessible to all other classes everywhere, assuming the class it resides in is accessible.
A constructor may be declared with the public access modifier, and if it is the constructor is accessible to all other classes everywhere, assuming the class it resides in is accessible.
protectedNoYesYesA member may be declared with the protected access modifier, and if so, is only accessible within its own package and also by a subclass of its class in other packages.
A constructor may be declared with the protected access modifier, and if so, it is only accessible to the package the implementation is in.

See the Packages lesson for more information on packaging.
See the Inheritance Basics lesson for more information on subclassing.
no modifier
package-private /
(the default)
YesYesYesIf a top-level class has no explicit access modifier, which is the default and is also known as package-private, it is accessible only within its own package.
If a member has no explicit access modifier it is only accessible within its own package.
If a constructor has no explicit access modifier, it is only accessible to the package the implementation is in.

See the Packages lesson for more information on packaging.
privateNoYesYesA member may be declared with the private access modifier, and if it is the member is only accessible within its own class.
A constructor may be declared with the private access modifier, and if it is the constructor can only be constructed from within its own class.

See the OO Concepts - Encapsulation lesson for example usage and information on how we use getters and setters to enforce tight encapsulation.

CouplingTop

By definition coupling is the degree to which one class has knowledge of another or in other words one class has a dependency upon another. Tight coupling occurs when a dependent concrete class contains a pointer to another concrete class that provides the required behavior and should be avoided. The problem here is that any changes to one class could impact the other and the person making the changes may be completely unaware of this and thus unknowingly break the class. So how do we avoid this scenario? We design by contract by using an interface to specify an API for other classes to use as discussed in the OO Concepts - Interfaces lesson or by using encapsulation as discussed in the OO Concepts - Encapsulation. The following code is an example of tight coupling and should be avoided:


/*
  Tight coupling example
*/ 
class A {
    int i;
    B b = new B();
    i = b.value;      // No encapsulation of this variable in class B!
}
class B {
    public int value; // Should be private and be accessed through public getters and setters
}

CohesionTop

Cohesion is the degree to which components of a class belong together to fit a particular role. What we want to avoid is low cohesion where a class incorporates several different aspects. A class that tries to do many things comes with higher maintenance and lower reusability.


/*
  Low cohesion example
*/ 
class AllInStaff {
    void getStaffSalary();
    void getStaffDetails();
    void getStaffSalesReport();
}
/*
  High cohesion example
*/ 
class Accounts {
    void getStaffSalary();
    ...
}
class Personnel {
    void getStaffDetails();
    ...
}
class SalesReporting {
    void getStaffSalesReport();
    ...
}

PolymorphismTop

Polymorphism, which roughly translated from the Greek means 'the ability to assume different forms'. So what does this mean for us Java programmers? To explain this we will refresh our memory about how we create an object:

object creation

So lets go through the three step process above:

  1. Declaration - Here. we declare a reference type named garbage of type Garbage and the JVM allocates space for it.
  2. Creation - Tells the JVM to allocate space on The Heap for a new Garbage object.
  3. Assignment - Assign the new Garbage object to the reference type garbage.

The reference type and the object type are the same.

Polymorphic Method InvocationTop

With polymorphism when you declare the reference type in the declaration, it can be from a superclass of the object we are creating. Or looking from a different perspective, when creating an object, we can use any supertype from a class that we have extended. The compiler has no problem with this but the power of polymorphism happens at runtime through the concept of virtual method invocation. This concept is the dynamic selection of overridden methods at runtime based on the actual object type, rather than the reference type. This only applies to instance methods, everything else uses the reference type at runtime.

object creation

So what does this give us I hear you ask? Well we can use the supertype for reference type declaration when instantiating our objects, safe in the knowledge that the JVM will use the actual object created to invoke the correct overridden methods of the subtypes at runtime.

See the OO Concepts - Polymorphism lesson for details.

Deeper Into PolymorphismTop

We can pass a polymorphic interface type as an argument and return a polymorphic interface type as well. An implemented abstract type is exactly the same as an overridden method type and so will use polymorphic method invocation to dynamically invoke the method of the the actual object type at runtime. This means we can group together objects from different hierarchies under the umbrella of the interface type as the reference type knowing the correct object will be always be invoked at runtime.

See the OO Concepts - Interfaces lesson for details.

Polymorphic RulesTop

There are some things to remember with regards to reference variables when using polymorphism:

  • A reference variable can only refer to one type and once declared is immutable although the object it refers to can change.
  • Like any other non final variables, reference variable can be reassigned to other objects.
  • The assigned reference variable can refer to the same type as the declared reference variable or any subtype of the declared reference variable.
  • Instance methods use the concept of virtual method invocation to dynamically invoke overridden methods at runtime based on the actual object type, rather than the declared reference type. This only applies to instance methods, everything else uses the declared reference type at runtime.
  • Reference variables can be declared as a class type or an interface type and if the reference variable is declared as an interface type it can reference any object of any class implementing the interface.

Object Reference CastingTop

In this part of the lesson we look at casting with regards to polymorphic types and when to use it. Lets get the terminology out of the way first:

  • downcasting - casting down the inheritance tree and has to be coded explicitly.
  • upcasting - casting up the inheritance tree and is implicit although we can code this ourselves.

We will look at some code to see when we need downcast and what happens when we cast wrongly or don't cast at all:



/*
 Casting Examples
*/ 
class A {
    void methodA() {
        System.out.println("method A class A"); 
    }
}

class B extends A {
    void methodA() {
        System.out.println("method A class B"); 
    }
    void methodB() {
        System.out.println("method B class B"); 
    }
}
/*
 Test classes for casting
*/ 
public class CastTest1 {
    public static void main (String[] args) {
        A a = new A();  
        B b = (B) a; 
    }
}
public class CastTest2 {
    public static void main (String[] args) {
        B b = new B();  
        A a1 = b; 
        A a2 = (A) b; 
    }
}
public class CastTest3 {
    public static void main (String[] args) {
        A [] aArray = { new A(), new B() };  
        for(A a : aArray) {
            a.methodA();
            if (a instanceof B) {
                a.methodB();
            }
        }
    }
}
public class CastTest4 {
    public static void main (String[] args) {
        A [] aArray = { new A(), new B() };  
        for(A a : aArray) {
            a.methodA();
            if (a instanceof B) {
                B b = (B) a;
                b.methodB();
            }
        }
    }
}

run Test casts

The above screenshot shows the output of compiling our A and B classes and then trying to compile/run the CastTest1, CastTest2, CastTest3 and CastTest4 classes.

When we compile CastTest1 it compiles fine as the compiler has to trust us when downcasting. When we run CastTest1 we get a ClassCastException as b is actually an instance of class A and supertypes know nothing about any subtypes.

When we compile and run CastTest2 it works fine, we are just showing implicit and explicit upcasting here.

The CastTest3 class doesn't compile as we haven't downcast our reference of a and class A doesn't have a method called methodB.

The CastTest4 class compile and works as we downcast our reference of a to a reference of class B which does have a method called methodB.


Preventing Inheritance/OverridingTop

There might be a scenario whereby, you need to stop a class from being inherited. We can stop inheritance occurring by using the final keyword in the class definition:


/*
  A final Class
*/ 
public final class A {

}
/*
  B Class
*/ 
public class B extends A {

}

run extinding final test

The above screenshot shows the result of trying to compile class B.

On a similar note there might be a situation whereby, you need to stop a method from being overridden. We can stop this happening by using the final keyword in the method definition:


/*
  A Class
*/ 
public class A {

    final void aMethod () {
        System.out.println("Marked final so we can't override this method.");
    }   
}
/*
  B Class
*/ 
public class B extends A {

    void aMethod () {
        System.out.println("This won't work.");
    }   
}

run extending final test

The above screenshot shows the result of trying to compile class B.


Constructor InheritanceTop

If you mark a superclass constructor with the private access modifier, any subclass that tries to extend it will fail with a compiler error when trying to access the superclass constructor with super(), either implicitly or explicitly. This of course defeats the whole point of inheritance, but is just something to be noted.

See OO Concepts - Superclass Constructors for detailed information on constructor inheritance.


Instance InheritanceTop

If you mark a superclass instance variable with the private access modifier it is not inherited by subclasses but can be accessed using public setters and getters of the superclass, which is the essence of encapsulation and of course is available for instantiation purposes through implicit or explicit calls to super(). Other accessibility to a superclass instance variable is dependant upon the access modifier used and packaging as described in the Access Modifiers table above.


Static InheritanceTop

If you mark a superclass static variable with the private access modifier it is not inherited by subclasses. Other accessibility to a superclass static variable is dependant upon the access modifier used and packaging as described in the Access Modifiers table above. Static methods that are accessible are also inherited by subclasses unless the subclass has a method with the same name and parameters which is known as method hiding and is discussed in OO Concepts - Static Overrides?


ConstructorsTop

Constructors allow us to instantiate our objects via declaration, assignment and creation.

object creation
  1. Declaration - Here. we declare a reference variable named moggy of type Cat and the JVM allocates space for it.
  2. Creation - Tells the JVM to allocate space on The Heap for a new Cat object.
  3. Assignment - Assign the new Cat object to the reference variable moggy.

Access Modifiers

The table below shows the types of access available in Java for constructors.

Access modifier Description
publicA constructor may be declared with the public access modifier, and if it is the constructor is accessible to all other classes everywhere, assuming the class it resides in is accessible.
protectedA constructor may be declared with the protected access modifier, and if so, it is only accessible to the package the implementation is in.

See the Packages lesson for more information on packaging.
no modifier
package-private /
(the default)
If a constructor has no explicit access modifier, it is only accessible to the package the implementation is in.

See the Packages lesson for more information on packaging.
privateA constructor may be declared with the private access modifier, and if it is the constructor can only be constructed from within its own class.

Constructor Checklist


  • A constructor runs when we code the new() operator followed by a class name.
  • Constructors must have the same name as the class and no return type.
  • Constructors are used to initialize the instance variables (object state) of the object instance being constructed.
  • If you don't code a constructor in your class the compiler will put in a default no arguments constructor.
  • If you do code a constructor in your class, the compiler will NOT put in a default no arguments constructor, you will have to code it yourself.
  • We can have more than one constructor in a class and the constructors are then known as overloaded constructors.
  • When using overloaded constructors in a class, each constructor must have different argument lists so the compiler knows which constructor to use to construct our objects. Having the same argument types is fine as long as the order differs.
  • You can refer to any member of the current object from within a non-static method or constructor by using the this() keyword.
  • You can invoke one constructor from another constructor within the same class by calling this() and doing so is known as explicit constructor invocation and is the only way to invoke a constructor.
  • If we decide to use this() it must be the first statement within our constructor or we get a compiler error. This means we can't use this() and super() together.
  • We can use super() to invoke a superclass constructor and if we don't supply this explicitly, then the compiler inserts a no-args super() for us as the first statement in the constructor if we haven't used the this() keyword.
  • When explicitly coding super() we can supply arguments to invoke a constructor in the superclass matching the signature of our call.
  • When explicitly coding super() it must be the first statement within the constructor or we get a compiler error. This means we can't use super() and this() together.
  • When explicitly coding super() only methods and static variables can be used within the call.
  • Interfaces do not have constructors as they are not part of a particular classes inheritance tree.
  • Abstract classes do have constructors, although we can't code them, as they are part of a classes inheritance tree and so are called via super() on concrete subclass instantiation.


/*
  Some code showing constructor usage
*/
public class A { 
    public static void main(String args[]) { 
        A a = new A(); // OK, compiler puts in a default no arguments constructor so we can instantiate
    }
}   

public class B { 
    B() { } // OK, we code our own default no arguments constructor
}                                                

public class C { 
    void C() { } // OK, this is a method with the same name as the class as it has a return type
    public static void main(String args[]) { 
        C c = new C();
    }
}          
                                     
public class D { 
    D() { }
    D(int i) { } // OK, overloaded constructor
    public static void main(String args[]) { 
        D d = new D();
        D d2 = new D(5);
    }
}

public class E { 
    int i;    
    String s;

    E(int i) {
        this(i, "unknown"); // Invoke one constructor from another constructor within the same class
    }
    E(int i, String s) {
        this.i = i;
        this.s = s;
    }
    public static void main(String args[]) { 
        E e = new E(5);
    }
}

/*
  Following code will fail as compiler inserts a no arguments constructor for us which invokes 
  super() and there isn't a no arguments constructor in superclass
*/
public class F extends E { 
}

public class G extends E { 
    G(int i) { // here we invoke existing super constructor so works fine 
        super(i);
    }
    public static void main(String args[]) { 
        G g = new G(5);
    }
}

IS-A and HAS-ATop

How do we know what to make a superclass or subclass and what goes into our instance variables for these classes. There is a very simple way to check, by using the IS-A and HAS-A test, to see what sort of relationships our classes and data members have. So how do we do this?

For the IS-A, anywhere where you would use the extends keyword just replace it with IS-A and see how the relationship looks.

For the HAS-A test, use the class followed by HAS-A and then the name of the possible instance variable and see how the relationship looks.

Try the following test to get to grips with IS-A and HAS-A relationships:

IS-A and HAS-A Test
Statement Question
Hammer extends Toolbox
  • valid
  • invalid
Hammer IS-A Toolbox - no.
Hammer HAS-A Toolbox - no.
Reversed
Toolbox IS-A Hammer - still no.
Toolbox HAS-A Hammer - yes.
Car extends Vehicle
  • valid
  • invalid
Car IS-A Vehicle - yes.
Car HAS-A Vehicle - no.
Sink extends Kitchen
  • valid
  • invalid
Sink IS-A Kitchen - no.
Sink HAS-A Kitchen - no.
Reversed
Kitchen IS-A Sink - still no.
Kitchen HAS-A Sink - yes.
Bird extends Canary
  • valid
  • invalid
Bird IS-A Canary - no.
Bird HAS-A Canary - no.
Reversed
Canary IS-A Bird - yes.
Canary HAS-A Bird - no.
Window extends House
  • valid
  • invalid
Window IS-A House - no.
Window HAS-A House - no.
Reversed
House IS-A Window - still no.
House HAS-A Window - yes.

Using IS-A and HAS-A are quite a simple way for checking inheritance relationships and the good thing is we can test for subclass suitability all the way down the inheritance tree.

Related java Tutorials

Objects & Classes - Reference Variables - The new Operator
Objects & Classes - Constructors
OO Concepts - Encapsulation
OO Concepts - Polymorphism
OO Concepts - Overriding Methods
Objects & Classes - Overloaded Methods
OO Concepts - Accessing Superclass Members
Inheritance Concepts - Superclass Constructors
OO Concepts - Static Overrides?
OO Concepts - IS-A and HAS-A Relationships

go to home page Homepage go to top of page Top