SimpleJava

3 Classes and Interfaces

3.1 Overriding vs. Overloading in Java

Overriding and Overloading are two very important concepts in Java. They are confusing for Java novice programmers. This post illustrates their differences by using two simple examples.

3.1.1 Definitions

Overloading occurs when two or more methods in one class have the same method name but different parameters.

Overriding means having two methods with the same method name and parameters (i.e., method signature). One of the methods is in the parent class and the other is in the child class. Overriding allows a child class to provide a specific implementation of a method that is already provided its parent class.

3.1.2 Overriding vs. Overloading

Here are some important facts about Overriding and Overloading:

  1. Real object type, not the reference variable's type, determines which overridden method is used at runtime. In contrast, reference type determines which overloaded method will be used at compile time.
  2. Polymorphism applies to overriding, not to overloading.
  3. Overriding is a run-time concept while overloading is a compile-time concept.

3.1.3 An Example of Overriding

Here is an example of overriding. After reading the code, guess the output.

class Dog{
    public void bark(){
        System.out.println("woof ");
    }
}
class Hound extends Dog{
    public void sniff(){
        System.out.println("sniff ");
    }

    public void bark(){
        System.out.println("bowl");
    }
}

public class OverridingTest{
    public static void main(String [] args){
        Dog dog = new Hound();
        dog.bark();
    }
}

Output:

bowl

In the example above, the dog variable is declared to be a Dog. During compile time, the compiler checks if the Dog class has the bark() method. As long as the Dog class has the bark() method, the code compilers. At run-time, a Hound is created and assigned to dog. The JVM knows that dog is referring to the object of Hound, so it calls the bark() method of Hound. This is called Dynamic Polymorphism.

3.1.4 An Example of Overloading

class Dog{
    public void bark(){
        System.out.println("woof ");
    }

    //overloading method
    public void bark(int num){
        for(int i=0; i<num; i++)
            System.out.println("woof ");
    }
}

In this overloading example, the two bark method can be invoked by using different parameters. Compiler know they are different because they have different method signature (method name and method parameter list).

3.2 What is Instance Initializer in Java?

In this post, I will first use an example to show what are instance variable initializer, instance initializer and static initializer and then illustrate how the instance initializer works in Java.

3.2.1 Execution Order

Look at the following class, do you know which one gets executed first?

public class Foo {

    //instance variable initializer
    String s = "abc";

    //constructor
    public Foo() {
        System.out.println("constructor called");
    }

    //static initializer
    static {
        System.out.println("static initializer called");
    }

    //instance initializer
    {
        System.out.println("instance initializer called");
    }

    public static void main(String[] args) {
        new Foo();
        new Foo();
    }
}

Output:

static initializer called
instance initializer called
constructor called
instance initializer called
constructor called

3.2.2 How does Java instance initializer work?

The instance initializer above contains a println statement. To understand how it works, we can treat it as a variable assignment statement, e.g., b = 0. This can make it more obvious to understand.

Instead of

int b = 0

, you could write

int b;
b = 0;

Therefore, instance initializers and instance variable initializers are pretty much the same.

3.2.3 When are instance initializers useful?

The use of instance initializers are rare, but still it can be a useful alternative to instance variable initializers if:

  1. initializer code must handle exceptions
  2. perform calculations that can't be expressed with an instance variable initializer.

Of course, such code could be written in constructors. But if a class had multiple constructors, you would have to repeat the code in each constructor.

With an instance initializer, you can just write the code once, and it will be executed no matter what constructor is used to create the object. (I guess this is just a concept, and it is not used often.)

Another case in which instance initializers are useful is anonymous inner classes, which can't declare any constructors at all. (Will this be a good place to place a logging function?)

Thanks to Derhein.

Also note that Anonymous classes that implement interfaces [1] have no constructors. Therefore instance initializers are neede to execute any kinds of expressions at construction time.

3.3 Why Field Can’t Be Overridden?

3.3.1 Can Field Be Overridden in Java?

Let's first take a look at the following example which creates two Sub objects. One is assigned to a Sub reference, the other is assigned to a Super reference.

package oo;

class Super {
    String s = "Super";
}

class Sub extends Super {
    String s = "Sub";
}

public class FieldOverriding {
    public static void main(String[] args) {
        Sub c1 = new Sub();
        System.out.println(c1.s);

        Super c2 = new Sub();
        System.out.println(c2.s);
    }
}

What is the output?

Sub
Super

We did create two Sub objects, but why the second one prints out "Super"?

3.3.2 Hiding Fields Instead Of Overriding Them

In [1], there is a clear definition of Hiding Fields:

Within a class, a field that has the same name as a field in the superclass hides the superclass's field, even if their types are different. Within the subclass, the field in the superclass cannot be referenced by its simple name. Instead, the field must be accessed through super. Generally speaking, we don't recommend hiding fields as it makes code difficult to read.

From this definition, member fields can not be overridden like methods. When a subclass defines a field with the same name, the subclass just declares a new field. The field in the superclass is hidden. It is NOT overridden, so it can not be accessed polymorphically.

3.3.3 Ways to Access Hidden Fields

  1. By using parenting reference type, the hidden parent fields can be access, like the example above.
  2. By casting you can access the hidden member in the superclass.
    System.out.println(((Super)c1).s);
    

3.4 Inheritance vs. Composition in Java

3.4.1 Inheritance

Let's suppose we have an Insect class. This class contains two methods: 1) move() and 2) attack().

class Insect {
    private int size;
    private String color;

    public Insect(int size, String color) {
        this.size = size;
        this.color = color;
    }

    public int getSize() {
        return size;
    }

    public void setSize(int size) {
        this.size = size;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public void move() {
        System.out.println("Move");
    }

    public void attack() {
        move(); //assuming an insect needs to move before attacking
        System.out.println("Attack");
    }
}

Now you want to define a Bee class, which is a type of Insect, but have different implementations of attack() and move(). This can be done by using an inheritance design like the following:

class Bee extends Insect {
    public Bee(int size, String color) {
        super(size, color);
    }

    public void move() {
        System.out.println("Fly");
    }

    public void attack() {
        move();
        super.attack();
    }
}
public class InheritanceVSComposition {
    public static void main(String[] args) {
        Insect i = new Bee(1, "red");
        i.attack();
    }
}

The class hierarchy diagram is as simple as: class-relation-1

out:

Fly
Fly
Attack

Fly" was printed twice, which indicates move() is called twice. But it should be called only ONCE.

The problem is caused by the super.attack() method. The attack() method of Insect invokes move() method. When the subclass calls super.attack(), it also invokes the overridden move() method.

To fix the problem, we can:

  1. eliminate the subclass's attack() method. This will make the subclass depends on the superclass's implementation of attack(). If the attack() method in the superclass is changed later (which is out of your control), e.g., the superclass's attack() method use another method to move, the subclass will need to be changed too. This is bad encapsulation.
  2. rewrite the attack() method like the following:
    public void attack() {
     move();
     System.out.println("Attack");
    }
    
    This would guarantee the correct result, because the subclass is not dependent on the superclass any more. However, the code is the duplicate of the superclass. (Image attack() method does complex things other than just printing a string) This does not following software engineering rule of reusing.

This inheritance design is bad, because the subclass depends on the implementation details of its superclass. If the superclass changes, the subclass may break.

3.4.2 Composition

Instead of inheritance, composition can be used in this case. Let's first take a look at the composition solution.

The attack function is abstracted as an interface.

interface Attack {
    public void move();
    public void attack();
}

Different kinds of attack can be defined by implementing the Attack interface.

class AttackImpl implements Attack {
    private String move;
    private String attack;

    public AttackImpl(String move, String attack) {
        this.move = move;
        this.attack = attack;
    }

    @Override
    public void move() {
        System.out.println(move);
    }

    @Override
    public void attack() {
        move();
        System.out.println(attack);
    }
}

Since the attack function is extracted, Insect does not do anything related with attack any longer.

class Insect {
    private int size;
    private String color;

    public Insect(int size, String color) {
        this.size = size;
        this.color = color;
    }

    public int getSize() {
        return size;
    }

    public void setSize(int size) {
        this.size = size;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }
}

Bee is a type of Insect, it can attack.

// This wrapper class wrap an Attack object
class Bee extends Insect implements Attack {
    private Attack attack;

    public Bee(int size, String color, Attack attack) {
        super(size, color);
        this.attack = attack;
    }

    public void move() {
        attack.move();
    }

    public void attack() {
        attack.attack();
    }
}

Class Diagram: class-design-1

public class InheritanceVSComposition2 {
    public static void main(String[] args) {
        Bee a = new Bee(1, "black", new AttackImpl("fly", "move"));
        a.attack();

        // if you need another implementation of move()
        // there is no need to change Insect, we can quickly use new method to attack

        Bee b = new Bee(1, "black", new AttackImpl("fly", "sting"));
        b.attack();
    }
}
fly
move
fly
sting

3.4.3 When to Use Which?

The following two items can guide the selection between inheritance and composition:

  1. If there is an IS-A relation, and a class wants to expose all the interface to another class, inheritance is likely to be preferred.
  2. If there is a HAS-A relationship, composition is preferred. In summary, Inheritance and composition both have their uses, and it pays to understand their relative merits.

3.5 Java Enum Examples

An enum in Java is just like any other class, with a predefined set of instances. Here are several examples to highlight how to use Java Enum.

3.5.1 Simple Example

public enum Color {
    RED, YELLOW, BLUE; //each is an instance of Color 
}
public class Test {
    public static void main(String[] args) {
        for(Color color: Color.values()){
            System.out.println(color);
        }
    }
}

Output:

RED
YELLOW
BLUE

3.5.2 With Constructor

public enum Color {
    RED(1), YELLOW(2), BLUE(3); //each is an instance of Color 

    private int value;

    private Color(){}

    private Color(int i){
        this.value = i;
    }

    //define instance method
    public void printValue(){
        System.out.println(this.value);
    }
}
public class Test {
    public static void main(String[] args) {
        for(Color color: Color.values()){
            color.printValue();
        }
    }
}
1
2
3

3.5.3 When to Use Java Enum?

Recall the definition of Java Enum which is like any other class, with a predefined set of instances.

A good use case is preventing the possibility of an invalid parameter. For example, imagine the following method:

public void doSomethingWithColor(int color);

This is ambiguous, and other developers have no idea how value to use. If you have an enum Color with BLACK, RED, etc. the signature becomes:

public void doSomethingWithColor(Color color);

Code calling this method will be far more readable, and can't provide invalid data.

3.6 4 types of Java inner classes

There are 4 different types of inner classes you can use in Java. The following gives their name and examples.

3.6.1 Static Nested Classes

class Outer {
    static class Inner {
        void go() {
            System.out.println("Inner class reference is: " + this);
        }
    }
}

public class Test {
    public static void main(String[] args) {
        Outer.Inner n = new Outer.Inner();
        n.go();
    }
}
Inner class reference is: Outer$Inner@19e7ce87

3.6.2 Member Inner Class

Member class is instance-specific. It has access to all methods, fields, and the Outer's this reference.

public class Outer {
    private int x = 100;

    public void makeInner(){
        Inner in = new Inner();
        in.seeOuter();
    }

    class Inner{
        public void seeOuter(){
            System.out.println("Outer x is " + x);
            System.out.println("Inner class reference is " + this);
            System.out.println("Outer class reference is " + Outer.this);
        }
    }

    public static void main(String [] args){
        Outer o = new Outer();
        Inner i = o.new Inner();
        i.seeOuter();
    }
}
Outer x is 100
Inner class reference is Outer$Inner@4dfd9726
Outer class reference is Outer@43ce67ca

3.6.3 Method-Local Inner Classes

public class Outer {
    private String x = "outer";

    public void doStuff() {
        class MyInner {
            public void seeOuter() {
                System.out.println("x is " + x);
            }
        }

        MyInner i = new MyInner();
        i.seeOuter();
    }

    public static void main(String[] args) {
        Outer o = new Outer();
        o.doStuff();
    }
}
x is outer
public class Outer {
    private static String x = "static outer";

    public static void doStuff() {
        class MyInner {
            public void seeOuter() {
                System.out.println("x is " + x);
            }
        }

        MyInner i = new MyInner();
        i.seeOuter();
    }

    public static void main(String[] args) {
        Outer.doStuff();
    }
}
x is static outer

3.6.4 Anonymous Inner Classes

This is frequently used when you add an action listener to a widget in a GUI application.

button.addActionListener(new ActionListener(){
     public void actionPerformed(ActionEvent e){
         comp.setText("Button has been clicked");
     }
});

3.7 What is Inner Interface in Java?

3.7.1 What is Inner Interface in Java?

Inner interface is also called nested interface, which means declare an interface inside of another interface. For example, the Entry interface is declared in the Map interface.

public interface Map {
    interface Entry{
        int getKey();
    }

    void clear();
}

3.7.2 Why Use Inner Interface?

There are several compelling reasons for using inner interface:

  • It is a way of logically grouping interfaces that are only used in one place.
  • It increases encapsulation.
  • Nested interfaces can lead to more readable and maintainable code.

One example of inner interface used in java standard library is java.util.Map and Java.util.Map.Entry. Here java.util.Map is used also as a namespace. Entry does not belong to the global scope, which means there are many other entities that are Entries and are not necessary Map's entries. This indicates that Entry represents entries related to the Map.

3.7.3 How Inner Interface Works?

To figure out how inner interface works, we can compare it with nested classes. Nested classes can be considered as a regular method declared in outer class. Since a method can be declared as static or non-static, similarly nested classes can be static and non-static. Static class is like a static method, it can only access outer class members through objects. Non-static class can access any member of the outer class. java-nested-1

Because an interface can not be instantiated, the inner interface only makes sense if it is static. Therefore, by default inter interface is static, no matter you manually add static or not.

3.7.4 A Simple Example of Inner Interface?

Map.java

public interface Map {
    interface Entry{
        int getKey();
    }

    void clear();
}

MapImpl.java

public class MapImpl implements Map {


    class ImplEntry implements Map.Entry{
        public int getKey() {
            return 0;
        }        
    }

    @Override
    public void clear() {
        //clear
    }
}

3.8 Constructors of Sub and Super Classes in Java

This post summarizes a commonly asked question about Java constructors.

3.8.1 Why creating an object of the sub class invokes also the constructor of the super class?

class Super {
    String s;

    public Super(){
        System.out.println("Super");
    }
}

public class Sub extends Super {

    public Sub(){
        System.out.println("Sub");
    }

    public static void main(String[] args){
        Sub s = new Sub();
    }
}

It prints:

Super
Sub

When inheriting from another class, super() has to be called first in the constructor. If not, the compiler will insert that call. This is why super constructor is also invoked when a Sub object is created.

This doesn't create two objects, only one Sub object. The reason to have super constructor called is that if super class could have private fields which need to be initialized by its constructor.

After compiler inserts the super constructor, the sub class constructor looks like the following:

    public Sub(){
        super();
        System.out.println("Sub");
    }

3.8.2 A Common Error Message: Implicit super constructor is undefined for default constructor

This is a compilation error message seen by a lot of Java developers:

"Implicit super constructor is undefined for default constructor. Must define an explicit constructor"

This compilation error occurs because the default super constructor is undefined. In Java, if a class does not define a constructor, compiler will insert a default no-argument constructor for the class by default. If a constructor is defined in Super class, in this case Super(String s), compiler will not insert the default no-argument constructor. This is the situation for the Super class above.

The constructors of the Sub class, either with-argument or no-argument, will call the no-argument Super constructor. Since compiler tries to insert super() to the 2 constructors in the Sub class, but the Super's default constructor is not defined, compiler reports the error message.

To fix this problem, simply 1) add a Super() constructor to the Super class like

public Super(){
    System.out.println("Super");
}

, or 2) remove the self-defined Super constructor, or 3) add super(value) to sub constructors.

3.8.3 Explicitly call super constructor in sub constructor

sub-constructor

The Sub constructor explicitly call the super constructor with parameter. The super constructor is defined, and good to invoke.

3.8.4 The Rule

In brief, the rules is: sub class constructor has to invoke super class instructor, either explicitly by programmer or implicitly by compiler. For either way, the invoked super constructor has to be defined.

3.9 Java Access Level for Members: public, protected, private

Java access level contains two parts: 1) access level for classes and 2) access level for members.

For class access level, the keyword can be public or no explicit modifier(package-private). For member access level, the keyword can be public, protected, package-private (no explicit modifier), or private.

The following table summarizes the access level of different modifiers for members. Access level determines the accessibility of fields and methods. It has 4 levels: public, protected, package-private (no explicit modifier), or private.

access-level

3.10 When to use private constructors in Java?

If a method is private, it means that it can not be accessed from any class other than itself. This is the access control mechanism provided by Java. When it is used appropriately, it can produce security and functionality. Constructors, like regular methods, can also be declared as private. You may wonder why we need a private constructor since it is only accessible from its own class. When a class needs to prevent the caller from creating objects. Private constructors are suitable. Objects can be constructed only internally.

One application is in the singleton design pattern. The policy is that only one object of that class is supposed to exist. So no other class than itself can access the constructor. This ensures the single instance existence of the class. Private constructors have been widely used in JDK, the following code is part of Runtime class.

public class Runtime {
    private static Runtime currentRuntime = new Runtime();

    public static Runtime getRuntime() {
        return currentRuntime;
    }

    // Don't let anyone else instantiate this class
    private Runtime() {
    }
}