Java: Object Orientation

From University, we know that object-oriented programming (OOP) is a programming paradigm which uses objects to design applications. When correctly used, principles of OOP like Encapsulation, Inheritance, Polymorphism, and Overriding/Overloading help developers to design flexible, extensible, and maintainable applications. When not used correctly, maintainability becomes expensive and flexibility compromised.

Nowadays OPP seems to be a basic subject, something everyone should know. But looking around and working on many projects, I have found that this is not completely true. There are uncountable applications which make a poor usage of OOP. In this sense, this article brings a quick review of OOP principles and best practices. The objective here is not to detail each aspect of OOP, but to present the main principles, advantages of good usage and some examples. These are the bases to advanced refactoring.

Encapsulation

Basically, encapsulation is used to allow programmers to make code changes without breaking another code. Encapsulation consists in not exposing implementation details of a class. But how to do that?

  • Creating public methods to access class’ instance variables
  • Protecting instance variables from exposure setting them private
  • Using JavaBeans convention

Here is an example that does not use Encapsulation:

package net.diegolemos.oop;

class NotEncapsulatedClass {
    public int attribute;
}

public class NotEncapsulated {
    public static void main(String... args) {
        NotEncapsulatedClass obj = new NotEncapsulatedClass();
        obj.attribute = -300;
    }
}

In the previous example, the instance variable attribute from class NotEncapsulated is visible for everyone, since it is declared public. There are many drawbacks when designing like that. First of all, no validation is possible when exposing a public variable. So other classes can assign any value to that variable, even a value that you are not waiting for. Secondly, cohesion and loose coupling are not respected, since other components know details from this object.

Next we are going to refactor the above example to apply the encapsulation principles.

package net.diegolemos.oop;

class EncapsulatedClass {
    private int attribute;

    public int getAttribute() {
        return attribute;
    }

    public void setAttribute(int attribute) {
        if(attribute < 0) {   // We don't want attribute smaller then zero
            this.attribute = 0;
        }
        else {
            this.attribute = attribute;
        }
    }
}

public class Encapsulated {
    public static void main(String... args) {
        EncapsulatedClass obj = new EncapsulatedClass();
        obj.setAttribute(-300);
    }
}

Inheritance

Inheritance is the mechanism by which a Java object can specialize another Java object members (instance variables and methods). In Java we can create inheritance relationships by extending a class (using keyword extends).

The common reasons to use inheritance are:

  • to use polymorphism
  • to reuse code

Reusing code is an essential aspect of OOP. Objects can inherit members of less-specialized objects avoiding code redundancy. Here is an example:

package net.diegolemos.oop;

class Person {
    void talk(){}
}

class Cop extends Person {
    void arrest(Person person){}
}

public class Inheritance {
    public static void main(String... args) {
        Person bob = new Person();
        Cop cop = new Cop();

        bob.talk();
        cop.talk();
        cop.arrest(bob);
    }
}

Polymorphism (many forms) allows specialized classes to be accessed without being necessarily known at compile time. When used correctly, code becomes clear and maintenance easier. We will talk about polymorphism later on this post.

Note: In order to avoid the “Deadly Diamond of Death” problem, Java does not support multiple inheritance. Each Java class can use the keyword extends over only one other class.

IS-A and HAS-A relationships

IS-A is an object relationship based on inheritance and interface implementation. If we have a class Plane and a class Boeing which inherits from Plane so we say that Boeing IS-A Plane. In Java, we express IS-A relationships through the keywords implements to implement an interface and extends inherit from a class.

A HAS-A relationship is characterized when an object uses another object as a member. Imagine that a class Computer has as instance variable an object Processor. So we know that Computer HAS-A Processor.

Polymorphism

In Java, Polymorphism is the ability of Java object to assume many forms. Any Java object that can pass more than one IS-A test can be considered polymorphic. As all Java objects inherit from class Object, they are all polymorphic since they pass the IS-A test for class Object and for their own type. Logically, this rule is not applicable for class Object itself.

To better understand Polymorphism, let’s remember a few concepts regarding reference variables:

  • Since a reference is a variable, it can be reassigned to any other object – when the variable is not declared final
  • We can declare a reference variable as a class type or as an interface type
  • A reference variable can have only one type (once declared, the type cannot be changed)
  • The methods that can be invoked on an object are determined by the reference variable’s type
  • A reference variable can refer to any object of the same type and to any subtype of the declared type

Since Java does not support multiple inheritance, implementing interfaces is a good manner to ensure object behaviour when the object has already a super class other than Object. Let’s see an example:

package net.diegolemos.oop;

import java.util.ArrayList;
import java.util.List;

class Bird {}

class Gull extends Bird implements Flyer {
    public void fly(){
        System.out.println("I can fly");
    }
}

class Chicken extends Bird {}

interface Flyer {
    void fly();
}

public class Polymorphism {
    public static void main(String... args) {
        Bird gull = new Gull();
        Bird chicken = new Chicken();

        List<Bird> birds = new ArrayLis<Bird>();
        birds.add(gull);
        birds.add(chicken);

        for(Bird bird : birds){
            if(bird instanceof Flyer) {
                ((Flyer) bird).fly();
            }
        }
    }
}

As shown above, the classes Gull and Chicken inherit from Bird class. Therefore, we used an interface called Flyer to give to some birds the ability to fly. Thanks to instanceof operator we can identify on runtime which birds can fly. In this example, the object Gull takes the form of the Flyer interface.

Overloading and Overriding

An overloaded method is a method that reuses the name of another existing one with different arguments and/or return type. Overloading a method is generally used when developers need to communicate a behaviour already offered by another method.

To overload a method we need to observe some points:

  • A method can be overloaded in the class or in a subclass
  • Overloaded methods must change the argument list
  • Overloaded methods can change the return type
  • Overloaded methods can change the access modifier
  • Overloaded methods can change the declared checked exceptions

Bellow an example of overloading.

package net.diegolemos.oop;

public class Overloading {
    int multiply(int a, int b) {
        return a * b;
    }

    double multiply(double a, double b) {
        return a * b;
    }

    public static void main(String... args) {
        Overloading calculator = new Overloading();

        calculator.multiply(5, 4);      // Will call the first one
        calculator.multiply(2.2, 4.4);  // Will call the second
    }
}

On the other side, override occurs when we have in a subclass a method with the same signature of a method inherited from a superclass (unless the superclass method is marked final). The main advantage in overriding is that we can attribute specific behaviour to a particular subclass.

When overriding methods some rules must be respected:

  • Methods marked final or static cannot be overridden
  • The parameter list must be the same from that of the overridden method (if it is not the case overload will occur)
  • The return type must be the same or a subtype of the return type of the original method
  • The overriding method cannot be more restrictive in its access modifiers than the method being overridden (if a method have public access the overriding method cannot be private)
  • A method can be overridden only if it is inherited from the super class (private and final methods are not overridden)
  • Overriding methods can throw unchecked (runtime) exceptions
  • The overriding method cannot throw checked exceptions that aren’t the same or a subtype of the exception of the overridden method
  • You must override abstract methods from abstract classes, unless the subclass is also abstract

Here follows an overriding example:

package net.diegolemos.oop;

class Fish {
    void swim() {
        System.out.print("Let's swim");
    }
}

class Sailfish extends Fish {
    @Override
    void swim() {
        super.swim();
        System.out.println(" fast");
    }
}

public class Overriding {
    public static void main(String... args) {
        Fish someFish = new Fish();
        Fish sailfish = new Sailfish();

        someFish.swim();
        sailfish.swim();
    }
}

Since Java 5, we have the @Override annotation to help us to override and not overload a method. If a method is annotated with the keyword @Override and it does not override any method, compilers will inform you 😉

This post presents a quick-overview of OOP possibilities using Java 6+. For a better understanding, I recommend the book SCJP Sun Certified Programmer for Java 6 Exam from Katherine Sierra and Bert Bates.

Advertisements

About Diego Lemos

Trainer, coach and polyglot programmer. Agile and software craftsmanship enthusiast.
This entry was posted in Java and tagged , . Bookmark the permalink.

One Response to Java: Object Orientation

  1. André says:

    Hi Lemos, good post.
    Some considerations:
    1) JavaBeans, implemented literally, it’s terrible. Maybe even so bad as lack of encapsulation, because can lead to an anemic model.
    2) Always favor composition over inhrerintance. Inhrerintance isn’t a good way to share code.
    Keep writing my frend!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s