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
- Develop code that implements tight encapsulation, loose coupling, and high cohesion in classes, and describe the benefits.
- 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.
- Explain the effect of modifiers on inheritance with respect to constructors, instance or static variables, and instance or static methods.
- 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.
- Develop code that implements "is-a" and/or "has-a" relationships.
- Develop code that implements tight encapsulation, loose coupling, and high cohesion in classes, and describe the benefits.
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 |
---|---|---|---|---|
public | Yes | Yes | Yes | A 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. |
protected | No | Yes | Yes | A 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) | Yes | Yes | Yes | If 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. |
private | No | Yes | Yes | A 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:

So lets go through the three step process above:
- Declaration - Here. we declare a reference type named
garbage
of typeGarbage
and the JVM allocates space for it. - Creation - Tells the JVM to allocate space on The Heap for a new
Garbage
object. - Assignment - Assign the new
Garbage
object to the reference typegarbage
.
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.

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();
}
}
}
}

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 {
}

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.");
}
}

The above screenshot shows the result of trying to compile class B
.
- You can't mark abstract classes as
final
or you will get a compiler error. Abstract classes, by definition, need to be extended.
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.

- Declaration - Here. we declare a reference variable named
moggy
of typeCat
and the JVM allocates space for it. - Creation - Tells the JVM to allocate space on The Heap for a new
Cat
object. - Assignment - Assign the new
Cat
object to the reference variablemoggy
.
Access Modifiers
The table below shows the types of access available in Java for constructors.
Access modifier | Description |
---|---|
public | 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. |
protected | 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. |
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. |
private | 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. |
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 usethis()
andsuper()
together. - We can use
super()
to invoke a superclass constructor and if we don't supply this explicitly, then the compiler inserts a no-argssuper()
for us as the first statement in the constructor if we haven't used thethis()
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 usesuper()
andthis()
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-A
Top
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:
Statement | Question | |
---|---|---|
Hammer extends Toolbox |
| 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 |
| Car IS-A Vehicle - yes. Car HAS-A Vehicle - no. |
Sink extends Kitchen |
| 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 |
| Bird IS-A Canary - no. Bird HAS-A Canary - no. Reversed Canary IS-A Bird - yes. Canary HAS-A Bird - no. |
Window extends House |
| 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