В чём разница между внутренними (inner) и статическими вложенными (static nested) классами

Вложенные (nested) классы в Java бывают двух видов: статические и нестатические. Если внутри одного класса объявить другой с модификатором static, то такой класс называется статически вложенным (static nested). Если просто объявить класс внутри другого, то такой класс называется внутренним (inner).

Статически вложенные классы (static nested)

Статически вложенный класс в общем и целом ведёт себя как обычный невложенный класс, как если бы OuterClass и InnerClass лежали в одном пакете. Основное отличие от обычных невложенных классах в том, как содаётся экземпляр такого класса:

То есть и тип переменной и вызов new для такого класса всегда помимо имени вложенного класса содержит и имя объемлющего.

Обычно такие классы создают, чтобы не плодить лишних файлов, когда точно известно, что вложенный класс всегда будет создаваться параллельно с объемлющим. Например, у вас есть класс Книга и класс Страница. Наверняка Страницы будут создаваться вместе с книгой и, возможно, логичней будет держать класс Страницы не в отдельном файле, а вложить в класс Книга.

Внутренние классы (inner)

Внутренний класс существует только в контексте объекта объемлющего класса. Таким образом сначала создаётся объект OuterClass, а затем с помощью специального синтаксиса объект InnerClass:

Как видно, тип переменной внутреннего класса также указывается через точку после имени объемлющего класса: OuterClass.InnerClass. Но оператор new используется необычным образом: outerObject.new InnerClass(). Так как новый объект внутреннего класса InnerClass существует в контексте конкретного объекта outerObject. Можно сделать другой объект типа OuterClass и в контексте этого другого объекта создать другой объект вложенного класса.

Объекты внутреннего класса редко используются за пределами объемлющего. То есть, хотя выше показано, как создать такой объект в любой точке кода, в реальности этим пользуются крайне редко. В подавляющем большинстве случаев объекты внутреннего класса используются внутри методов объемлющего, и синтаксис там намного проще:

Обратите внимание, в конструкторе объемлющего класса экземпляр вложенного создаётся самым обычным способом: this.fullName = new FullName();

Нам оказалось удобно внутри класса Passport объединить несколько полей (name и lastName) во внутренний класс FullName с собственными методами. Мы это сделали и внутри объемлющего класса просто оперируем переменной FullName fullName, а не полями name и lastName