Classes can be defined inside other classes to encapsulate logic and constrain the context of use. For example:

TestOrder



Member inner class

The type of the nested class depends on the context in which it is used. Static nested class (OrderToShip) is associated with the static context of the Account outer class.

package ex6;

import java.util.HashSet;
import java.util.Set;

interface Order {
    void addItem(String productName, int qty);

    int getOrderNumber();

    Set<Item> getItems();
}

interface Item {
    String getItemNumber();

    String getProductName();

    int getQty();
}

class Account {
    public static Order createOrderToShip() {
        return new OrderToShip();
    }

    // increment for each order
    private static int last_order_number;

    private static class OrderToShip implements Order {
        private int last_item_no; // increment for each item in the order

        private int orderNumber = ++last_order_number;

        @Override
        public int getOrderNumber() {
            return this.orderNumber;
        }

        private Set<Item> items = new HashSet<>();

        @Override
        public Set<Item> getItems() {
            return this.items;
        }

        @Override
        public void addItem(String productName, int qty) {
            items.add(new OrderItem(productName, qty));
        }

        private class OrderItem implements Item {
            private String productName;
            private int qty;
            private String itemId = orderNumber+"_"+ ++last_item_no;

            @Override
            public String getItemNumber() {
                return this.itemId;
            }

            @Override
            public String getProductName() {
                return this.productName;
            }

            @Override
            public int getQty() {
                return this.qty;
            }

            private OrderItem(String productName, int qty) {
                this.productName = productName;
                this.qty = qty;
            }
        }

    }

}

public class OrderTest {
    public static void main(String[] args) {
        Order order1 = Account.createOrderToShip();
        order1.addItem("A", 1);
        order1.addItem("B", 2);

        System.out.println("order Number: " + order1.getOrderNumber());
        order1.getItems()
            .forEach(x -> System.out.println(x.getItemNumber()+" => " + x.getProductName()+" : "+x.getQty()));
       
        Order order2 = Account.createOrderToShip();
        order2.addItem("X", 1);
        order2.addItem("Y", 2);
        order2.addItem("Z", 1);

        System.out.println("order Number: " + order2.getOrderNumber());
        order2.getItems()
            .forEach(x -> System.out.println(x.getItemNumber()+" => " + x.getProductName()+" : "+x.getQty()));
       
    }
}

Static and member nested classes can be defined as:

  • public, protected, or default - can be accessed externally
  • private - can be referenced only inside their outer class

Member inner class OrderItem is associated with the instance context of the outer class OrderToShip.

Local inner class

Local inner class is associated with the context of specific method. For example, you can move OrderItem to the addItem() method as follows:

@Override
public void addItem(String itemName, int itemQty) {
  final class OrderItem implements Item {
    private String productName=itemName;
    private int qty = itemQty;
    private String itemId = orderNumber+"_"+ (items.size()+1);

    @Override
    public String getItemNumber() {
      return this.itemId;
    }

    @Override
    public String getProductName() {
      return this.productName;
    }

    @Override
    public int getQty() {
      return this.qty;
    }
    // no need of constructor
    // private OrderItem(String productName, int qty) {
    //     this.productName = productName;
    //     this.qty = qty;
    // }
  }
  items.add(new OrderItem());
}

At this level, the class can have only final or abstract modifiers. Local inner classes directly can access outer method (addItem()) local variables and parameters if they are final or effectively final.

Anonymous inner class

Anonymous inner class is an inline implementation or extension of an interface or a class.

@Override
public void addItem(final String itemName, int itemQty) {

  items.add( new Item() { // Anonymous Inner class
    private String productName=itemName;
    private int qty = itemQty;
    private String itemId = orderNumber+"_"+ (OrderToShip.this.items.size()+1);

    @Override
    public String getItemNumber() {
      return this.itemId;
    }

    @Override
    public String getProductName() {
      return this.productName;
    }

    @Override
    public int getQty() {
      return this.qty;
    }
    // no need of constructor
    // private OrderItem(String productName, int qty) {
    //     this.productName = productName;
    //     this.qty = qty;
    // }
  });
  //items.add(new OrderItem());
}

Although most of the time inner classes are created for the interfaces, you can create an anonymous inner class for the abstract class as well.

package ...;

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

public class Ex7 {
    public static void main(String[] args) {
        List<Book> books = new ArrayList<>();
        books.add(new Book("My book", 100){

            @Override
            public int findMiddlePage() {
                
                return this.pages/2;
            }
            
        });
        books.forEach(x -> System.out.println(x.toString()+" middle is :"+x.findMiddlePage()));
    }
}

abstract class Book {
    String name;
    int pages;
    public Book(String name, int pages){
        this.name=name;
        this.pages = pages;
    }

    abstract public int findMiddlePage();

    @Override
    public String toString(){
        return this.name+" with pages: "+this.pages;
    }

}