Java Class DesignJ8 Home « Java Class Design

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 Oracle Java SE 8 Programmer II for this part of the certification.

  • Java Class Design
    1. Implement encapsulation.
    2. Implement inheritance including visibility modifiers and composition.
    3. Implement polymorphism.
    4. Override hashCode, equals, and toString methods from Object class.
    5. Create and use singleton classes and immutable classes.
    6. Develop code that uses static keyword on initialize blocks, variables, methods, and classes.

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.

Inheritance OverviewTop

Inheritance is a language mechanism, that allows us to group common characteristics together into a set of related items, which we can then redefine into more specific subsets. in Java terminology, the general class that holds the group of common characteristics is known as the superclass and the specialised subset of this is known as the subclass. We achieve this hierarchial connection in Java using the extends keyword, wherein the subclass extends the superclass. By extending like this we have created an inheritance hierarchy where the subclass inherits the traits of the superclass, in other words the members of the superclass, assuming the access modifiers permit inheritance. Because the subclass is more specialised it can override the instance methods members of the superclass and also add new members of its own.

When we override the instance method members of the superclass in a subclass, we are writing a more specific method for use by the subclass that overrides the inherited method. We will go into overriding in much more detail in Overriding Methods later in the lesson.

We do not override instance variables in a subclass with the same identifier in the superclass. This is known as hiding and is discussed in the Inheritance Concepts - Superclass Members section.

We do not override static methods in a subclass with the same identifier in the superclass as these are class methods and not related to any instance. This is also known as hiding and is discussed, paradoxically in the lesson on Polymorphism. Following is a slideshow to digest the information we have written so far about inheritance:

inheritance 1

All is good in Vehicle Town, we have our vehicle class and everyone is happy. But then, people without cars want to get on a bus and people want trucks to transport goods. Let's use inheritance, rather than write new classes and replicate a lot of things that all vehicles can do.

inheritance 2


The first thing we do is make a Car class, which is all our original Vehicle class held; so we don't need to change anything here.

inheritance 3



Our new Bus class comes next and we need a new instance variable conductor.

inheritance 4


Last is our Truck class, in which we are going to override the carry() method from the Vehicle class.

The code for these classes is available in the OO Concepts - Inheritance Basics lesson.

IS-A and HAS-A Relationships Top

Before we code up our new Truck subclasses we need to talk about the relationships in our inheritance trees. 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
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
  • invalid
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. Lets take part of the inheritance tree from the slideshow above and check our new level of subclasses to make sure they pass the IS-A tests for all their superclasses before we code them up.

inheritance 9

Well our new HGV class passes the IS-A tests for being a Truck and Vehicle so our inheritance tree looks good. We can also do the same for the Garbage class and it passes as well. We can now code up our new Truck subclasses:

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.

Class Access Modifiers

The table below shows the types of access available in Java for our top-level classes.

Access modifier Description
publicA top-level class may be declared with the public access modifier, and if it is the class is accessible to all other classes everywhere.
no modifier
package-private / (the default)
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.
  • The only access modifier that can be used with a top-level class is public.
  • Only one class can be marked as public per source file.
  • If a class within a source file is marked as public then the name of the source file must exactly match the name of the public class (followed by the .java extension).
  • A source file can include multiple non-public classes.
  • If a source file contains no public class then there is no naming constraints on that source file, ie. it doesn't have to match the name of one of the non-public classes.
  • Aside from access modifiers classes can also be marked with the non-access modifiers abstract, final and strictfp. The strictfp is not part of the certification and so we won't go into details. When a class is marked as abstract it has to be subclassed; when a class is marked as final it can't be subclassed, so these two modifers can never be used together.
  • A class can extend a maximum of one other class.


/*
  Some code showing class rules
*/
class A { }                        // OK
private class A { }                // No, can only use public access modifier with top level class
protected class A { }              // No, can only use public access modifier with top level class
public class A { }                 // OK
public class B extends A { }       // OK
public class C extends B, A { }    // No, can only extend one class
abstract class D { }               // OK
final class E { }                  // OK
abstract final class F { }         // No, cannot use both abstract and final


Method Access Modifiers

The table below shows the four types of access available in Java for members (variables, inner classes and methods).

Access modifier Description
publicA 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.
protectedA 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..

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)
If a member has no explicit access modifier it is only accessible within its own package.
privateA member may be declared with the private access modifier, and if it is the member is only accessible within its own class.

Non-access Modifiers

The table below shows the types of non-access modifiers available for use with variables.

static variable and instance variable modifiers local variable modifiers
final
transient
volatile
final

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?


Inheritance Checklist Top


  • We create an inheritance hierarchy using the extends keyword wherein the subclass extends the superclass.
  • The subclass inherits the members of the superclass, assuming the access modifiers permit inheritance.
  • We can use the IS-A test to verify that the inheritance hierarchy is valid and this test works from the subclass up the inheritance chain of superclasses, but not the other way around.
  • We can override the inherited method members as long as we adhere to the following rules:
    • A method override must have exacly the same arguments as the superclass method with the same method name or you end up with an overloaded method.
    • A method override must have the same return type (or co-variant thereof) as the superclass method with the same method name.
    • When we override a method, the overriding method can't be less accessible, but can be more accessible.
    • You cannot override a method marked with the final modifier.
    • You cannot override a method marked with the private access modifier. Even if you have a method in the subclass with the same method name and arguments as a private method in the superclass it knows nothing of that method as it can't see it and is just a normal non-overriding method
    • You cannot override a method marked with the static modifier as overridden methods pertain to instance methods and not the class. In fact if you use the same static method and parameters in a subclass as a static method in a superclass this is known as method hiding and is discussed in Static Overrides?.
  • We can access members from the superclass above the subclass using super.memberName.
  • 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.

There are other rules regarding exceptions declarations when using overrides and these are discussed in the Exceptions section when we look at Overridden Methods & Exceptions.

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.

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