Nested and Inner Classes

We can define classes inside other classes. They’re called nested classes.

The nested class can be non-static or static. If it is non-static it is called an inner class.  If it is static, it is called a static nested class.

Inner Classes

An inner class can access all of the members of the enclosing class, but the outer class (as written below) cannot access the members of the inner class unless an instance of the inner class is created within the outer class.  Below is a class named Outer that contains an inner class named Inner.  The driver shows how we can create and use instances of both the outer class and the inner class within the driver.

class Outer {
    int x = 10;

    void incrementX() { 
        x++; 
    }
    void print() {
        System.out.printf("outer: %d\n", x); 
    }
    class Inner {
        int y = 20;
        
        void incrementY() {
            y++;
        }
        void print() { 
            System.out.printf("inner: %d\n", y);
        }
        void incrementAndPrintAll() {
            incrementX();
            incrementY();
            Outer.this.print();
            print();
        }
    }
}
class Driver {
    public static void main(String[] args) {
        Outer outer = new Outer();
        System.out.println(outer.x);                    // prints "10"
        outer.incrementX();
        outer.print();                                  // prints "outer: 11"

        Outer.Inner inner = outer.new Inner();
        System.out.println(inner.y);                    // prints "20"
        inner.incrementY();
        inner.print();                                  // prints "inner: 21"
                                  
        // We cannot access Outer's members using the inner object except within the class
        inner.incrementAndPrintAll();                   // prints "outer: 12"
                                                        //        "inner: 22"
    }
}

Private Inner Classes

<< in progress >>

Static Nested Classes – The Builder Pattern

The Builder Pattern is a pattern that is used to provide flexibility in constructing an object when the object has many fields (more than 4), some of which are required when constructing a new instance of the class, and other that have default values that don’t necessarily have to be changed when creating a new instance of the class.

Lets look at an example class named Vehicle that uses the Builder Pattern. Inside the class we have a public static Builder class that users use to create instances of the Vehicle class.  The Builder class has a constructor that includes parameters for the fields that the user must provide values for.  The class also has various methods that change the values of the other fields.  These are similar to setter methods, except these methods return the instance of the builder class on which the method was called so that the setters can be chained together.  The last method named build creates a new instance of Vehicle by passing into the private Vehicle constructor the Builder object and using the values in the Builder object to initialize the fields in the Vehicle class.

import java.awt.Color; 

public class Vehicle {     
    private int year; 
    private String make; 
    private String model; 
    private boolean extendedCab; 
    private int numberOfCylinder; 
    private Color color;
 
    public static class Builder { 
        // Required parameters in the constructor     
        private int year; 
        private String make; 
        private String model; 

        // Optional parameters are initialized to default values 
        private boolean extendedCab = false; 
        private int numberOfCylinder = 6; 
        private Color color = Color.BLACK; 

        public Builder(int year, String make, String model) { 
            this.year = year; 
            this.make = make; 
            this.model = model; 
        } 
        public Builder extendedCab(boolean val) { 
            this.extendedCab = val; 
            return this; 
        } 
        public Builder numberOfCylinders(int val) { 
            this.numberOfCylinders = val; 
            return this; 
        } 
        public Builder color(Color color) { 
            this.color = color; 
            return this; 
        } 
        public Vehicle build() { 
            return new Vehicle(this); 
        } 
    } 

    // private prevents users from calling this constructor explicitly 
    private Vehicle(Builder builder) { 
        year = builder.year; 
        make = builder.make; 
        model = builder.model; 
        extendedCab = builder.extendedCab; 
        numberOfCylinders = builder.numberOfCylinders; 
        color = builder.color; 
    } 
    
    public int getYear() { return year; }
    public String getMake() { return make; }
    public String getModel() { return model; }
    public boolean hasExtendedCab() { return extendedCab; }
    public int getNumberOfCylinders() { return numberOfCylinders; }
    public Color getColor() { return color; }
}

Each instance of the Vehicle class is created by the following sequence of calls:

  1. The Builder constructor is called passing in the required fields: year, make, and model.  The result is a new Builder object.
  2. Various methods are called on the builder object which sets the optional fields of the object.  Each returns back the same Builder object on which it was called (this).
  3. Last, the build() method is called on the Builder object which creates and returns an instance of the private Vehicle class using the values in the fields of the Builder object.
Vehicle v1 = new Vehicle.Builder(2005, "Ford", "Ranger")
    .extendedCab(true)                                    
    .build();

Vehicle v2 = new Vehicle.Builder(2017, "Ford", "F-150")
    .numberOfCylinders(8)
    .color(RED)
    .build();

© 2017 – 2019, Eric. All rights reserved.