Skip to content
wannacode

Classes inside classes

Problem

A class that only makes sense in one place

In the lesson about the linked list, Node was a separate class. Anyone in the program can make nodes, but they only make sense in the list. The same goes for an order line:

class OrderLine {
String product;
int quantity;
double price;
}
class Order {
List<OrderLine> lines = new ArrayList<>();
}

OrderLine without Order makes no sense. The name is long so it does not clash with the line of an invoice, InvoiceLine, or with the line of a warehouse. And nothing says that the two classes go together. Can the line live inside the order?

The idea

Static nested class

It can. A class declared inside another class is called a nested class:

class Order {
static class Line {
String product;
int quantity;
double price;
Line(String product, int quantity, double price) {
this.product = product;
this.quantity = quantity;
this.price = price;
}
double total() {
return quantity * price;
}
}
private List<Line> lines = new ArrayList<>();
void add(Line line) {
lines.add(line);
}
double total() {
double sum = 0;
for (Line line : lines) {
sum += line.total();
}
return sum;
}
}
Order order = new Order();
order.add(new Order.Line("Coffee beans", 2, 12.5));
order.add(new Order.Line("Mug", 4, 3.0));
System.out.println("Total: €" + order.total());
Output
Total: €37.0
  • From the outside, the name is Order.Line. Inside Order, it is just Line.
  • static means that the line is not tied to a specific order. new Order.Line(...) is made without an Order object, like a regular class.
  • If it is private static class Line, nobody outside Order can use it.

You have already met such a class: Map.Entry from the HashMap lesson is nested in Map. And LinkedList in Java keeps its nodes in a private static class Node.

The idea

Inner class: it sees the outer object

The cart has a discount for all products in it. Each product must know the discount of its own cart. Then the class has no static:

class Cart {
private int discountPercent;
private List<Item> items = new ArrayList<>();
Cart(int discountPercent) {
this.discountPercent = discountPercent;
}
class Item {
String name;
double price;
Item(String name, double price) {
this.name = name;
this.price = price;
}
double finalPrice() {
return price * (100 - discountPercent) / 100;
}
}
void add(String name, double price) {
items.add(new Item(name, price));
}
void print() {
for (Item item : items) {
System.out.println(item.name + ": €" + item.finalPrice());
}
}
}
Cart cart = new Cart(10);
cart.add("Headphones", 80);
cart.add("Case", 20);
cart.print();
Output
Headphones: €72.0
Case: €18.0
A nested class without static is called an inner class. Each Item is created by a specific cart and sees its fields, even the private ones. finalPrice reads the discountPercent of the cart it is in.

Cart cart = new Cart(10);
cart.add("Headphones", 80);
cart.add("Case", 20);
cart.print();
Step by step, in words
  1. The cart is a Cart object with a 10% discount.
  2. add creates an Item. Besides name and price, each Item keeps a hidden arrow to the cart that created it: Cart.this.
  3. The second Item points to the same cart.
  4. finalPrice of "Headphones" follows the hidden arrow and reads discountPercent: 80 * (100 - 10) / 100 = 72.0.
  5. finalPrice of "Case" follows the hidden arrow and reads discountPercent: 20 * (100 - 10) / 100 = 18.0.
More precisely, if you are curious
  • From the outside, an inner object is created through the object of the outer class: cart.new Item(...). The syntax is rare, because usually the outer class creates the inner ones itself, like add here:
Cart cart = new Cart(10);
Cart.Item cable = cart.new Item("Cable", 10);
System.out.println(cable.finalPrice());
Output
9.0
  • Each inner object keeps a hidden reference to the outer one. If the fields of the two classes have the same name, you write the outer one as Cart.this.discountPercent.
  • If the nested class does not use the fields of the outer class, make it static. That way it does not keep a reference it does not need.

Try it

Two carts

The Cart class is the same. One cart has no discount, and the other has 20% for club members.

What will the program print?

Cart regular = new Cart(0);
Cart member = new Cart(20);
regular.add("Book", 15);
member.add("Book", 15);
regular.print();
member.print();
Answer

Try it

Find the bug

The line should show the currency of the order. The program does not compile:
Compile error
Main.java:9: error: non-static variable currency cannot be referenced from a static context
return product + ": " + currency + price;
^

Which line has the problem?

Pick the line with the bug.

The idea

A class in a method

A class can also be declared in a method. Then it is only visible there and is called a local class:

static void printBestSeller(String[] names, int[] sold) {
class Sale {
String name;
int count;
Sale(String name, int count) {
this.name = name;
this.count = count;
}
}
Sale best = new Sale(names[0], sold[0]);
for (int i = 1; i < names.length; i++) {
if (sold[i] > best.count) {
best = new Sale(names[i], sold[i]);
}
}
System.out.println("Best seller: " + best.name + " (" + best.count + " sold)");
}
String[] names = { "Coffee", "Tea", "Water" };
int[] sold = { 120, 45, 300 };
printBestSeller(names, sold);
Output
Best seller: Water (300 sold)

Sale keeps the name and the count together. Nobody uses it outside printBestSeller, so it lives there. Such classes are rare, but it is good to know them.

The idea

A class with no name

The discounts in the shop are an interface:

interface Discount {
double apply(double price);
}

So far you made a class for each discount: class BlackFriday implements Discount { ... }. If the discount is used in only one place, the class can be written right there, with no name:

Discount blackFriday = new Discount() {
@Override
public double apply(double price) {
return price * 70 / 100;
}
};
Discount none = new Discount() {
@Override
public double apply(double price) {
return price;
}
};
System.out.println(blackFriday.apply(100));
System.out.println(none.apply(100));
Output
70.0
100.0

new Discount() { ... } does two things. It declares a class with no name that implements Discount, and it creates one object of it. Such a class is called an anonymous class. Inside, it is like a regular class: @Override and the methods of the interface.

More precisely, if you are curious
  • An anonymous class can also extend a class, not only implement an interface: new Product("Coffee", 12.5) { ... } calls the constructor of Product and overrides methods.
  • An anonymous class can read the local variables of the method it is written in, but only if they do not change after that.
  • An interface with a single method, like Discount, will be written much shorter in the "Modern Java" module. The anonymous class is the step toward that.

The idea

Comparator: sorting by your own rule

Collections.sort sorted text and numbers. But it cannot sort students:

Collections.sort(students);

The compiler says no suitable method found for sort(List<Student>). It does not know what to sort students by: by name, by grade, by age?

The rule is given separately, as a Comparator. It is an interface with one method, compare(a, b). It returns a negative number if a should be before b, zero if they are equal, and a positive number if a is after b. It is handy as an anonymous class:

class Student {
String name;
double grade;
Student(String name, double grade) {
this.name = name;
this.grade = grade;
}
@Override
public String toString() {
return name + " " + grade;
}
}
Collections.sort(students, new Comparator<Student>() {
@Override
public int compare(Student a, Student b) {
return Double.compare(b.grade, a.grade);
}
});
System.out.println(students);
Output
[Maria 6.0, Ivan 5.5, Ana 5.5, Peter 4.75]
  • Double.compare(b.grade, a.grade) compares the other way around, so the highest grade comes first.
  • Ivan and Ana have the same grade and stay in the order they were in. Collections.sort does not swap equal elements.
  • For text and numbers in reverse order there is a ready-made Comparator: Collections.reverseOrder().
List<String> cities = new ArrayList<>();
cities.add("Sofia");
cities.add("Varna");
cities.add("Plovdiv");
Collections.sort(cities, Collections.reverseOrder());
System.out.println(cities);
Output
[Varna, Sofia, Plovdiv]
More precisely, if you are curious
  • Comparator is in java.util, with a type parameter: Comparator<Student> compares students.
  • Text and numbers are sorted without a Comparator, because String and Integer implement the Comparable interface with a compareTo method. Your class can implement it too, if it has one natural order. Comparator is for cases where there are several orders.
  • Double.compare and Integer.compare protect you from mistakes. With int, a.age - b.age looks shorter, but with very large numbers the subtraction overflows and the sign flips.

Try it

Which one is first?

What will the program print?

List<Product> products = new ArrayList<>();
products.add(new Product("Monitor", 220));
products.add(new Product("Mouse", 25));
products.add(new Product("Keyboard", 60));
Collections.sort(products, new Comparator<Product>() {
@Override
public int compare(Product a, Product b) {
return Double.compare(a.price, b.price);
}
});
for (Product p : products) {
System.out.println(p.name);
}
Answer

Exercise

Classes that go together

Solve the tasks in your editor.

1. Shopping cart. A ShoppingCart class with a static nested class Item (name and price) and the methods addItem(Item item), getTotal() and count(). In main, add new ShoppingCart.Item("Milk", 2.5), bread for €1.20 and coffee for €7.50.

Output
Products: 3
Total: €11.2

2. Web server monitor. A WebServer class with a static nested class Request (address and status code) and the methods addRequest(Request r) and printStats(). The stats say how many requests there are, how many there are of each status code (sorted) and what percent are successful, that is, with a code from 200 to 299. The requests are /home 200, /cart 200, /login 401, /home 200, /api/orders 500.

Output
Requests: 5
200: 3
401: 1
500: 1
Successful: 60%

3. Building manager. A Building class with a static nested class Apartment (number, number of rooms, owner) and the methods addApartment(Apartment a) and getApartmentInfo(int number). Keep the apartments in a Map<Integer, Apartment>. Add 101 with 3 rooms owned by Ivanova and 102 with 2 rooms owned by Petrov. Look up 101 and 999.

Output
Apartment 101: 3 rooms, owner Ivanova
There is no apartment 999.

4. Ranking. Sort the students from the lesson by grade, from the highest down. When the grades are equal, sort them by name, in alphabetical order. It all goes in one Comparator: if the comparison by grade is not 0, return it, otherwise compare the names with compareTo.

Output
Maria 6.0
Ana 5.5
Ivan 5.5
Peter 4.75

More tasks

  • Computer configurator. A Computer class with the static nested classes CPU (brand, speed in GHz), RAM (GB) and Storage (GB) and the methods setCPU, setRAM, setStorage. toString describes the configuration: Intel 3.5 GHz, 16 GB RAM, 512 GB.
  • Trip planner, again. Do the task from the HashMap lesson with a nested class TripPlanner.Destination that keeps the name and the list of activities.
  • Contact network, again. Move Contact from the task in the HashMap lesson inside ContactNetwork. Should it be static?
  • Mental math, again. Move Question from the task in the lesson about lists inside MathGame.
  • Products in two ways. Sort a list of products by price from the most expensive, and by name when the price is the same. Then sort the same list by name only, in reverse order.
  • Your own discount. Write three anonymous Discount classes: 10% over €100, a fixed €5 off and "second product at half price". Apply them to one and the same cart.

Test

Check yourself

1. What is the difference between static class Line and class Item inside another class?
2. Why does a static nested class not see the regular fields of the outer class?
3. What does new Discount() { … } create?
4. compare(a, b) returns a negative number. What does that mean for Collections.sort?
5. Which one sorts the students by grade, from the highest down?

Made by Ivo Gergov as part of Evo Tech's campaign for accessible programming education ·