W10. Абстрактные классы, интерфейсы (interfaces), UML-диаграммы классов, пакеты (packages)
1. Краткое содержание
1.1 Абстрактные классы и методы
Abstract class — класс с ключевым словом abstract, который нельзя создать напрямую через new. Это общий чертёж: общие свойства и поведение заданы, но «жить» в программе должен уже конкретный тип — как вместо абстрактной «еды» на стол попадают яблоки или шоколад, так вместо абстрактного Vehicle создаются машина, мотоцикл и т.д.
1.1.1 Зачем нужны abstract classes
- Logical grouping: родственные классы собираются под общим зонтиком — например,
Lion,Cat,Dogпод абстрактнымAnimal. - Enforcing implementation: можно объявить abstract methods — без тела; любой concrete (неабстрактный) subclass обязан их реализовать, сохраняя контракт и разрешая разную реализацию.
1.1.2 Объявление abstract class
Абстрактный класс помечается abstract:
abstract class Vehicle {
// Regular fields (common to all vehicles)
Color color;
int numWheels;
// Abstract method - no implementation
abstract void startEngine();
// Concrete method - has implementation
void honk() {
System.out.println("Beep beep!");
}
}1.1.3 Abstract methods
Abstract method — объявление метода без тела (без фигурных скобок с кодом); ключевое слово abstract и точка с запятой:
abstract void startEngine();Так задаётся контракт: у каждого транспорта должен быть способ start engine, но реализация зависит от конкретного типа.
1.1.4 Основные правила
- Abstract class нельзя инстанцировать напрямую: недопустимо
new Vehicle(). - Если в классе есть хотя бы один abstract method, класс должен быть
abstract. - В одном abstract class сочетаются abstract и concrete (обычные) методы.
- Abstract methods в классе не обязаны быть (редкий случай).
- Concrete subclass реализует все унаследованные abstract methods или сам остаётся
abstract. - У abstract class могут быть конструкторы,
staticиfinalметоды. - Поля — с любыми модификаторами (
private,protected,public, package-private).
1.1.5 Реализация
Неабстрактный класс, расширяющий abstract class, должен дать тела всем abstract methods:
class Motorcycle extends Vehicle {
@Override
void startEngine() {
System.out.println("Kick start!");
}
}Теперь Motorcycle — concrete class, допустимо Motorcycle bike = new Motorcycle();.
Если подкласс не реализовал все abstract methods, он остаётся abstract:
abstract class FlyingVehicle extends Vehicle {
// startEngine() is still not implemented
// So FlyingVehicle must be abstract
}1.2 Final classes и методы
Ключевое слово final ограничивает расширяемость: запрещает наследование или переопределение (в зависимости от контекста).
1.2.1 Final class
Final class нельзя расширять: после final ни один класс не может extends этот класс:
final class Human extends Animal {
// ... implementation
}
// This would cause a compiler error:
// class SuperHuman extends Human { } // ERROR!1.2.2 Final methods
Final method нельзя override в подклассах — поведение зафиксировано для всей иерархии:
class Animal {
final void breathe() {
System.out.println("Breathing oxygen");
}
}
class Dog extends Animal {
// This would cause a compiler error:
// void breathe() { } // ERROR! Cannot override final method
}1.2.3 Final fields
Final field (constant в смысле однократной инициализации) можно присвоить только один раз после инициализации:
public class Circle {
private final double radius; // Must be initialized
public Circle(double r) {
radius = r; // Initialization in constructor
}
// radius cannot be changed after this point
}По конвенции static final поля класса пишут в UPPER_SNAKE_CASE:
public static final double PI = 3.14159265359;1.3 Interfaces
Interface — ссылочный тип в Java, задающий contract: набор сигнатур методов, которые обязан предоставить implementing class. В отличие от abstract class, интерфейс ближе к pure abstraction: описано что сделать, а не как.
1.3.1 Зачем interfaces
- Multiple «inheritance» of type: множественного наследования классов в Java нет, но класс может implement несколько интерфейсов —
DuckиSwimmable, иFlyable. - Standardization: контракты вроде
Comparableсразу говорят, что объект можно сравнивать. - Decoupling: код может зависеть от интерфейса, а не от concrete class — проще менять реализации и тестировать.
1.3.2 Объявление interface
Ключевое слово interface:
interface Swimmable {
void swim(); // Abstract method (implicitly public abstract)
void stopSwimming(); // Abstract method
}
interface Flyable {
void fly();
void stopFlying();
}Начиная с Java 8, в interface допускаются:
- Default methods: реализация по умолчанию с ключевым словом
default. - Static methods: утилиты на уровне самого интерфейса.
interface Living {
default void live() {
System.out.println(this.getClass().getSimpleName() + " lives");
}
}1.3.3 implements
Класс подключает интерфейсы через implements (несколько — через запятую):
class Duck implements Swimmable, Flyable, Living {
@Override
public void swim() {
System.out.println("Duck is swimming");
}
@Override
public void stopSwimming() {
System.out.println("Duck stopped swimming");
}
@Override
public void fly() {
System.out.println("Duck is flying");
}
@Override
public void stopFlying() {
System.out.println("Duck stopped flying");
}
// live() method is inherited from Living interface (default method)
}1.3.4 Свойства interface
- Методы по умолчанию implicitly public abstract (кроме
defaultиstatic). - Поля в interface — implicitly public static final (константы).
- Класс может implement несколько интерфейсов, но extends только один класс.
- Интерфейс может extends другой интерфейс (или несколько).
- Интерфейс не может
extendsкласс. - Интерфейс нельзя инстанцировать через
new.
1.4 Abstract class vs. interface
Важно понимать, когда выбирать abstract class, а когда interface.
1.4.1 Ключевые отличия
| Аспект | Abstract class | Interface |
|---|---|---|
| Methods | Abstract и обычные методы вместе | До Java 8 — только abstract; с Java 8+ ещё default и static |
| Fields | Любые поля по модификаторам и «изменяемости» | Фактически только константы (public static final) |
| Multiple inheritance | Один суперкласс | Несколько интерфейсов |
| Implementation | Может частично реализовать контракт через код в классе | Не «реализует» abstract class; задаёт контракт |
| Keyword | abstract |
interface |
| Связь | extends для класса |
У класса — implements; у интерфейса — extends другого интерфейса |
| Access modifiers | Любые для членов | Публичный контракт по умолчанию |
| Когда | Общий код и поля у тесно связанных типов («is a») | Общее поведение у слабо связанных типов |
1.4.2 Как выбирать
Abstract class, если:
- нужно разделить общий код между родственными классами;
- много общих методов и полей;
- нужны
private/protectedчлены; - нужно изменяемое состояние (не только константы).
Interface, если:
- интерфейс могут реализовать несвязанные классы;
- важен контракт поведения, а реализация вторична;
- нужно множественное наследование типа;
- нужен чистый контракт без обязательной базовой реализации.
1.5 Проверка типов и приведение
1.5.1 Static и dynamic type
У каждой ссылки на объект два типа:
- Static type — тип в объявлении; на этапе компиляции не меняется.
- Dynamic type — фактический класс объекта в heap; может меняться при присваивании.
Animal a = new Lion(); // Static type: Animal, Dynamic type: Lion
a = new Frog(); // Static type: Animal, Dynamic type: Frog1.5.2 Upcasting
Upcasting — приведение ссылки от подкласса к суперклассу; безопасно и часто неявно, потому что объект подкласса «is a» суперкласс:
Lion lion = new Lion();
Animal animal = lion; // Upcasting (implicit) - always safe1.5.3 Downcasting
Downcasting — от суперкласса к подклассу; нужно явное приведение и возможен сбой во время выполнения, если dynamic type не совместим:
Animal animal = new Lion();
Lion lion = (Lion) animal; // Downcasting (explicit) - safe here
Animal animal2 = new Frog();
Lion lion2 = (Lion) animal2; // Runtime error! ClassCastException1.5.4 Оператор instanceof
Для безопасного downcasting проверяют dynamic type через instanceof:
instanceof_operator: obj instanceof ClassNametrue, если obj — экземпляр ClassName или его subclass; иначе false.
Animal a = new Lion();
if (a instanceof Lion) {
Lion lion = (Lion) a; // Safe to cast
// Use lion-specific methods
} else if (a instanceof Frog) {
Frog frog = (Frog) a; // Safe to cast
// Use frog-specific methods
}1.6 UML: диаграммы классов
UML (Unified Modeling Language) — стандартизованный язык моделирования ПО. Class diagram — один из ключевых видов: классы, атрибуты, методы и связи между ними.
1.6.1 Класс на диаграмме
Класс — прямоугольник из трёх зон:
- Верх: имя класса (жирным).
- Середина: атрибуты (поля).
- Низ: методы.
Модификаторы видимости в UML:
+—public-—private#—protected~— package-private (по умолчанию)
Пример:
Person
-name: String
-age: int
+Person(initialName: String)
+printPerson(): void
+getName(): String
1.6.2 Abstract class на UML
Пометка <<abstract>> или курсив; abstract methods — курсивом:
<<abstract>> Shape
-coords: Coords
+move(): void
+draw(): void
1.6.3 Interface на UML
Стереотип <<interface>>:
<<interface>> Movable
+moveUp(): void
+moveDown(): void
+moveLeft(): void
+moveRight(): void
1.6.4 Static и final
- Static — подчёркивание.
- Final поля —
{readOnly}илиUPPER_SNAKE_CASE. - Final методы —
{leaf}.
1.6.5 Связи между классами
1. Association Общая связь «использует / взаимодействует».
- Направление стрелки: кто «знает» о ком.
- Cardinality: сколько объектов участвует (
1,0..1,*,1..*). - Метка: пояснение связи (по желанию).
2. Inheritance (generalization) «Is a»; сплошная линия с полым треугольником к суперклассу.
3. Realization (implementation) Класс implements interface; пунктир с полым треугольником к интерфейсу.
4. Composition Сильное «has a»: часть не существует без целого; при уничтожении целого гибнут части. Закрашенный ромб со стороны целого.
Пример: Building и Room — снос здания уничтожает комнаты как части модели.
5. Aggregation Слабое «has a»: часть может существовать отдельно. Пустой ромб со стороны целого.
Пример: Car и Wheel — колёса можно снять, они остаются объектами.
6. Dependency «Uses»: зависимость, часто через параметр метода или локальную переменную. Пунктирная стрелка.
1.7 Packages
Package — пространство имён для классов и интерфейсов: меньше конфликтов имён, проще управлять доступом.
1.7.1 Зачем packages
В больших проектах разные авторы могут выбрать одно имя класса; пакеты разводят имена по namespace и помогают структурировать библиотеку типов.
1.7.2 Объявление package
В начале файла — package:
package myPackage;
public class MyClass {
// ...
}Fully-qualified name класса: myPackage.MyClass.
1.7.3 Вложенные пакеты
Вложенность задаётся точками в имени пакета:
package company.department.lab.math;
public class Calculator {
// ...
}Полное имя: company.department.lab.math.Calculator.
1.7.4 Видимость и пакеты
publicкласс — из любого пакета.- Класс без модификатора — только внутри своего пакета (package-private).
package myPackage;
public class PublicClass { // Visible everywhere
// ...
}
class PackageClass { // Visible only in myPackage
// ...
}1.7.5 import
Чтобы сослаться на public класс из другого пакета:
- Полное имя (fully-qualified name):
util.math.MathVector v = new util.math.MathVector();- Импорт конкретного класса:
import util.math.MathVector;
MathVector v = new MathVector(); // Can now use short name- Import-on-demand (
*):
import util.math.*;
MathVector v = new MathVector();
Calculator c = new Calculator();Стандартную библиотеку часто подключают так:
import java.util.*;
import java.io.*;1.7.6 Именование
Для распространяемых библиотек принято обратное DNS-имя организации:
org.wonderful.very.util.math
Так снижается риск глобальных коллизий имён.
1.7.7 Пакеты и каталоги
Имя пакета отражается в пути: company.department.project → дерево каталогов:
base_directory/company/department/project/
На Windows путь может выглядеть как base_directory\company\department\project\.
1.8 Сводка по модификаторам
Четыре уровня видимости в Java:
| Модификатор | Класс | Пакет | Подкласс | Везде |
|---|---|---|---|---|
public |
✓ | ✓ | ✓ | ✓ |
protected |
✓ | ✓ | ✓ | ✗ |
| по умолчанию (package-private) | ✓ | ✓ | ✗ | ✗ |
private |
✓ | ✗ | ✗ | ✗ |
Кратко:
private: только свой класс.- По умолчанию: весь текущий пакет.
protected: пакет + наследники (в т.ч. в других пакетах).public: откуда угодно.
2. Определения
- Abstract class: класс с
abstract, не создаётся черезnew; может содержать abstract methods. - Abstract method: объявление без тела; реализуется в concrete subclasses.
- Concrete class: неабстрактный класс с полной реализацией унаследованных abstract methods; допускает
new. - Final class: класс с
final— запрет на subclassing. - Final method: метод с
final— запрет на overriding. - Final field: поле с однократной инициализацией (константа в этом смысле).
- Interface: контракт методов для implementing classes; ключевое слово
interface. - Default method: метод interface с реализацией по умолчанию (
default, Java 8+). - Static type: тип ссылки в объявлении; не меняется на этапе компиляции.
- Dynamic type: фактический класс объекта во время выполнения; может меняться при присваивании.
- Upcasting: подкласс → суперкласс; безопасно, часто неявно.
- Downcasting: суперкласс → подкласс; явное приведение; риск
ClassCastException. - instanceof: проверка, является ли объект экземпляром класса или subclass / interface во время выполнения.
- UML: стандартизованный язык моделирования ПО.
- Class diagram: диаграмма классов UML.
- Association: связь «использует / взаимодействует».
- Inheritance (generalization): «is a», наследование.
- Realization (implementation): класс реализует interface.
- Composition: сильное «has a», часть не живёт без целого.
- Aggregation: слабое «has a», часть независима.
- Dependency: «uses», зависимость (параметры, локальные переменные).
- Cardinality: числа на связях UML.
- Package: пространство имён для типов.
- Fully-qualified name: полное имя с путём пакета (например,
java.util.ArrayList). - Import statement: краткие имена классов из других пакетов.
3. Примеры
3.1. Иерархия существ: abstract class (Лаба 9, Задание 1)
Создайте abstract class Creature с abstract methods bear() и die(), полем String name = null и boolean isAlive = false. Добавьте неабстрактный метод shoutName(): если name != null, печатать имя, иначе — сообщение об ошибке.
Классы Human, Dog и Alien наследуют Creature и по-разному переопределяют abstract methods:
- в
bear()каждый класс задаёт имя и печатает строку вида"The [class name] [name] was born"; - в
die()— строку"The [class name] [name] has died".
У класса Dog добавьте метод bark().
Класс AbstractClassDemonstration должен демонстрировать работу.
Нажмите, чтобы увидеть решение
Идея: Abstract class задаёт общий каркас и общее поведение, а subclasses дополняют его конкретными реализациями abstract methods.
// Creature.java
abstract class Creature {
// Fields common to all creatures
String name = null;
boolean isAlive = false;
// Abstract methods - must be implemented by subclasses
abstract void bear();
abstract void die();
// Non-abstract method with implementation
void shoutName() {
if (name != null) {
System.out.println(name);
} else {
System.out.println("Error: Creature has no name!");
}
}
}
// Human.java
class Human extends Creature {
@Override
void bear() {
System.out.print("Enter human name: ");
// For demonstration, we'll assign directly
this.name = "Alice";
this.isAlive = true;
System.out.println("The Human " + name + " was born");
}
@Override
void die() {
this.isAlive = false;
System.out.println("The Human " + name + " has died");
}
}
// Dog.java
class Dog extends Creature {
@Override
void bear() {
System.out.print("Enter dog name: ");
// For demonstration, we'll assign directly
this.name = "Buddy";
this.isAlive = true;
System.out.println("The Dog " + name + " was born");
}
@Override
void die() {
this.isAlive = false;
System.out.println("The Dog " + name + " has died");
}
// Dog-specific method
void bark() {
System.out.println(name + " says: Woof! Woof!");
}
}
// Alien.java
class Alien extends Creature {
@Override
void bear() {
System.out.print("Enter alien name: ");
// For demonstration, we'll assign directly
this.name = "Zorg";
this.isAlive = true;
System.out.println("The Alien " + name + " was born");
}
@Override
void die() {
this.isAlive = false;
System.out.println("The Alien " + name + " has died");
}
}
// AbstractClassDemonstration.java
public class AbstractClassDemonstration {
public static void main(String[] args) {
// Create different creatures
Human human = new Human();
Dog dog = new Dog();
Alien alien = new Alien();
// Demonstrate birth
human.bear();
dog.bear();
alien.bear();
System.out.println("\n--- Shouting Names ---");
// Demonstrate shoutName()
human.shoutName();
dog.shoutName();
alien.shoutName();
// Demonstrate dog-specific behavior
System.out.println("\n--- Dog Behavior ---");
dog.bark();
System.out.println("\n--- Death ---");
// Demonstrate death
human.die();
dog.die();
alien.die();
// Test shoutName with no name
System.out.println("\n--- Unnamed Creature ---");
Creature unnamed = new Human(); // Not born yet
unnamed.shoutName(); // Should print error
}
}Обсуждение: Почему Creature dog = new Dog(); dog.bark(); не компилируется?
Ответ: Static type переменной dog — Creature, компилятор знает только методы Creature. Несмотря на dynamic type Dog, вызовы проверяются по static type. Для bark() нужен downcasting: ((Dog) dog).bark(); или тип переменной Dog.
3.2. Массив существ и полиморфизм (Лаба 9, Задание 1, продолжение)
Измените класс AbstractClassDemonstration из Задания 1: создайте массив (или коллекцию) существ разных типов (Human, Dog, Alien) и для каждого элемента вызовите bear() и die().
Нажмите, чтобы увидеть решение
Идея: Polymorphism позволяет хранить в массиве типа суперкласса объекты разных subclasses и вызывать переопределённые методы с dynamic dispatch.
import java.util.ArrayList;
public class AbstractClassDemonstration {
public static void main(String[] args) {
// Using ArrayList for flexibility (can also use array)
ArrayList<Creature> creatures = new ArrayList<>();
// Add different types of creatures
creatures.add(new Human());
creatures.add(new Dog());
creatures.add(new Alien());
creatures.add(new Human());
creatures.add(new Dog());
System.out.println("=== Birth of Creatures ===");
// Call bear() for each creature
for (Creature creature : creatures) {
creature.bear(); // Dynamic dispatch - correct method called
}
System.out.println("\n=== Creatures Shouting Their Names ===");
for (Creature creature : creatures) {
creature.shoutName();
}
System.out.println("\n=== Death of Creatures ===");
// Call die() for each creature
for (Creature creature : creatures) {
creature.die(); // Dynamic dispatch - correct method called
}
// Alternative using traditional array
System.out.println("\n\n=== Using Array Instead ===");
Creature[] creatureArray = new Creature[3];
creatureArray[0] = new Human();
creatureArray[1] = new Dog();
creatureArray[2] = new Alien();
for (int i = 0; i < creatureArray.length; i++) {
creatureArray[i].bear();
}
for (int i = 0; i < creatureArray.length; i++) {
creatureArray[i].die();
}
}
}Ответ: Polymorphism здесь означает, что на этапе компиляции не нужно фиксировать конкретный тип каждого элемента: для каждого объекта вызываются bear() и die() той версии, которая соответствует его dynamic type.
3.3. Final-классы и промежуточный Animal (Лаба 9, Задание 2)
Расширьте предыдущее решение:
- введите класс
Animal, наследующийCreature; - пусть
HumanиDogнаследуютAnimal, а не напрямуюCreature; - запретите дальнейшее наследование от
HumanиDog(сделайте их final).
Нажмите, чтобы увидеть решение
Идея: final у класса запрещает дальнейшее наследование — поведение иерархии «заморожено» снизу вверх от этого класса.
// Creature.java (unchanged)
abstract class Creature {
String name = null;
boolean isAlive = false;
abstract void bear();
abstract void die();
void shoutName() {
if (name != null) {
System.out.println(name);
} else {
System.out.println("Error: Creature has no name!");
}
}
}
// Animal.java - intermediate abstract class
abstract class Animal extends Creature {
// Can add animal-specific fields or methods here
int age = 0;
void eat() {
System.out.println(name + " is eating");
}
}
// Human.java - now inherits from Animal and is final
final class Human extends Animal {
@Override
void bear() {
this.name = "Alice";
this.isAlive = true;
System.out.println("The Human " + name + " was born");
}
@Override
void die() {
this.isAlive = false;
System.out.println("The Human " + name + " has died");
}
}
// Dog.java - now inherits from Animal and is final
final class Dog extends Animal {
@Override
void bear() {
this.name = "Buddy";
this.isAlive = true;
System.out.println("The Dog " + name + " was born");
}
@Override
void die() {
this.isAlive = false;
System.out.println("The Dog " + name + " has died");
}
void bark() {
System.out.println(name + " says: Woof! Woof!");
}
}
// Alien.java - still inherits from Creature directly
class Alien extends Creature {
@Override
void bear() {
this.name = "Zorg";
this.isAlive = true;
System.out.println("The Alien " + name + " was born");
}
@Override
void die() {
this.isAlive = false;
System.out.println("The Alien " + name + " has died");
}
}
// This would cause a compilation error:
// class SuperHuman extends Human { } // ERROR: Cannot subclass final class
// This would also cause an error:
// class SuperDog extends Dog { } // ERROR: Cannot subclass final class
// Demonstration
public class FinalClassDemonstration {
public static void main(String[] args) {
Human human = new Human();
Dog dog = new Dog();
Alien alien = new Alien();
human.bear();
dog.bear();
alien.bear();
System.out.println("\n--- Using Animal Methods ---");
human.eat(); // Inherited from Animal
dog.eat(); // Inherited from Animal
// alien.eat(); // ERROR: Alien is not an Animal
System.out.println("\n--- Death ---");
human.die();
dog.die();
alien.die();
}
}Ответ: final у Human и Dog не позволяет менять их поведение через наследование — это полезно для целостности дизайна, безопасности и предсказуемости оптимизаций.
3.4. Интерфейсы Swimmable, Flyable и Living (Лаба 9, Задание 3)
Интерфейс Swimmable с методами swim() и stopSwimming().
Интерфейс Flyable с методами fly() и stopFlying().
Интерфейс Living с default method live(), печатающим "[class name] lives".
Класс Submarine — implements Swimmable с переопределением методов.
Класс Duck — implements Swimmable, Flyable, Living; переопределите все не-default методы.
Класс Penguin — implements Swimmable, Living; переопределите не-default методы.
Класс InterfaceDemonstration демонстрирует сценарии.
Подсказка: stopSwimming / stopFlying имеют смысл только если сущность уже «в процессе».
Нажмите, чтобы увидеть решение
Идея: Interfaces дают множественное наследование поведения; default methods — общую реализацию, доступную всем implementing classes.
// Swimmable.java
interface Swimmable {
void swim();
void stopSwimming();
}
// Flyable.java
interface Flyable {
void fly();
void stopFlying();
}
// Living.java
interface Living {
default void live() {
System.out.println(this.getClass().getSimpleName() + " lives");
}
}
// Submarine.java
class Submarine implements Swimmable {
private boolean isSwimming = false;
@Override
public void swim() {
if (!isSwimming) {
isSwimming = true;
System.out.println("Submarine is submerging and swimming underwater");
} else {
System.out.println("Submarine is already swimming");
}
}
@Override
public void stopSwimming() {
if (isSwimming) {
isSwimming = false;
System.out.println("Submarine has surfaced and stopped swimming");
} else {
System.out.println("Submarine is not swimming");
}
}
}
// Duck.java
class Duck implements Swimmable, Flyable, Living {
private boolean isSwimming = false;
private boolean isFlying = false;
@Override
public void swim() {
if (isFlying) {
System.out.println("Duck cannot swim while flying!");
return;
}
if (!isSwimming) {
isSwimming = true;
System.out.println("Duck is swimming on the water");
} else {
System.out.println("Duck is already swimming");
}
}
@Override
public void stopSwimming() {
if (isSwimming) {
isSwimming = false;
System.out.println("Duck stopped swimming");
} else {
System.out.println("Duck is not swimming");
}
}
@Override
public void fly() {
if (isSwimming) {
System.out.println("Duck cannot fly while swimming!");
return;
}
if (!isFlying) {
isFlying = true;
System.out.println("Duck is flying in the sky");
} else {
System.out.println("Duck is already flying");
}
}
@Override
public void stopFlying() {
if (isFlying) {
isFlying = false;
System.out.println("Duck stopped flying and landed");
} else {
System.out.println("Duck is not flying");
}
}
// live() method is inherited from Living interface (default method)
}
// Penguin.java
class Penguin implements Swimmable, Living {
private boolean isSwimming = false;
@Override
public void swim() {
if (!isSwimming) {
isSwimming = true;
System.out.println("Penguin is swimming gracefully underwater");
} else {
System.out.println("Penguin is already swimming");
}
}
@Override
public void stopSwimming() {
if (isSwimming) {
isSwimming = false;
System.out.println("Penguin stopped swimming and waddled to shore");
} else {
System.out.println("Penguin is not swimming");
}
}
// live() method is inherited from Living interface (default method)
}
// InterfaceDemonstration.java
public class InterfaceDemonstration {
public static void main(String[] args) {
System.out.println("=== Submarine Demonstration ===");
Submarine sub = new Submarine();
sub.swim();
sub.stopSwimming();
sub.stopSwimming(); // Try to stop when not swimming
System.out.println("\n=== Duck Demonstration ===");
Duck duck = new Duck();
duck.live(); // Using default method from Living
duck.swim();
duck.fly(); // Cannot fly while swimming
duck.stopSwimming();
duck.fly();
duck.swim(); // Cannot swim while flying
duck.stopFlying();
duck.swim(); // Now can swim
duck.stopSwimming();
System.out.println("\n=== Penguin Demonstration ===");
Penguin penguin = new Penguin();
penguin.live(); // Using default method from Living
penguin.swim();
penguin.swim(); // Already swimming
penguin.stopSwimming();
}
}Ответ: Набор interfaces задаёт несколько ролей поведения: у Duck совмещены плавание и полёт, у Penguin — только плавание; Submarine показывает, что «неживой» объект тоже может реализовать поведенческий контракт Swimmable.
3.5. Массив объектов Living (Лаба 9, Задание 3, продолжение)
Измените InterfaceDemonstration: создайте массив «живых» объектов разных типов (Duck, Penguin) и для каждого вызовите live().
Обсуждение:
- что произойдёт при попытке вызвать
swim()для элементов такого массива; - можно ли положить в массив экземпляр
Submarine.
Нажмите, чтобы увидеть решение
Идея: Массив типа interface даёт полиморфизм по общему контракту, даже если dynamic types различаются.
public class InterfaceDemonstration {
public static void main(String[] args) {
// Create array of Living objects
Living[] livingThings = new Living[2];
livingThings[0] = new Duck();
livingThings[1] = new Penguin();
System.out.println("=== All Living Things ===");
for (Living thing : livingThings) {
thing.live(); // All Living things have this method
}
// Discussion Question 1: What if we call swim()?
System.out.println("\n=== Attempting to Swim ===");
for (Living thing : livingThings) {
// This won't compile directly:
// thing.swim(); // ERROR: Living doesn't have swim()
// We need to check and cast:
if (thing instanceof Swimmable) {
((Swimmable) thing).swim();
} else {
System.out.println(thing.getClass().getSimpleName() +
" cannot swim");
}
}
// Discussion Question 2: Can Submarine be added?
System.out.println("\n=== Can Submarine be in Living array? ===");
// This won't compile:
// livingThings[0] = new Submarine(); // ERROR!
// Submarine doesn't implement Living interface
System.out.println("No, Submarine cannot be added to Living[] " +
"because Submarine doesn't implement Living interface");
// But we can create a Swimmable array:
System.out.println("\n=== Swimmable Array ===");
Swimmable[] swimmers = new Swimmable[3];
swimmers[0] = new Duck();
swimmers[1] = new Penguin();
swimmers[2] = new Submarine(); // This works!
for (Swimmable swimmer : swimmers) {
swimmer.swim();
}
for (Swimmable swimmer : swimmers) {
swimmer.stopSwimming();
}
}
}Ответы на вопросы для обсуждения:
- Что при вызове
swim()? У ссылки static typeLiving, методаswim()в контракте нет — вызвать напрямую нельзя. Нужноinstanceof Swimmableи затем downcasting кSwimmable. - Можно ли положить
Submarine? Нет: массивLiving[]принимает только объекты с dynamic type, реализующимLiving. УSubmarineнетLiving— ошибка компиляции. ЗатоSubmarineможно хранить, например, вSwimmable[].
3.6. Проверки типов с instanceof (Лекция 9, проверки типов)
Покажите безопасный downcasting с instanceof на иерархии Animal (Lion, Frog) и отдельном классе Car (неродственном типу).
Нажмите, чтобы увидеть решение
Идея: instanceof — это RTTI: проверка dynamic type перед downcasting, чтобы избежать ClassCastException.
// Base class
class Animal {
public int f1 = 100;
public void makeSound() {
System.out.println("Some animal sound");
}
}
// Derived classes
class Lion extends Animal {
public int f2 = 200;
@Override
public void makeSound() {
System.out.println("Roar!");
}
public void hunt() {
System.out.println("Lion is hunting");
}
}
class Frog extends Animal {
public int f3 = 300;
@Override
public void makeSound() {
System.out.println("Ribbit!");
}
public void jump() {
System.out.println("Frog is jumping");
}
}
// Unrelated class
class Car {
public void drive() {
System.out.println("Car is driving");
}
}
// Demonstration
public class TypeCheckDemo {
public static void main(String[] args) {
// Creating objects with different dynamic types
Animal a1 = new Lion();
Animal a2 = new Frog();
System.out.println("=== Type Checking ===");
// Check what a1 is
boolean r1 = a1 instanceof Animal; // true
boolean r2 = a1 instanceof Lion; // true
boolean r3 = a1 instanceof Frog; // false
System.out.println("a1 instanceof Animal: " + r1);
System.out.println("a1 instanceof Lion: " + r2);
System.out.println("a1 instanceof Frog: " + r3);
System.out.println("\n=== Safe Downcasting ===");
// Safe downcasting using instanceof
if (a1 instanceof Lion) {
Lion lion = (Lion) a1; // Safe to cast
System.out.println("a1 is a Lion, accessing f2: " + lion.f2);
lion.hunt();
} else if (a1 instanceof Frog) {
Frog frog = (Frog) a1;
System.out.println("a1 is a Frog, accessing f3: " + frog.f3);
frog.jump();
}
if (a2 instanceof Lion) {
Lion lion = (Lion) a2;
lion.hunt();
} else if (a2 instanceof Frog) {
Frog frog = (Frog) a2; // Safe to cast
System.out.println("a2 is a Frog, accessing f3: " + frog.f3);
frog.jump();
}
System.out.println("\n=== Unsafe Downcasting ===");
// This would compile but throw ClassCastException at runtime:
try {
Lion lion = (Lion) a2; // a2 is actually a Frog!
lion.hunt();
} catch (ClassCastException e) {
System.out.println("Error: Cannot cast Frog to Lion - " +
e.getMessage());
}
System.out.println("\n=== Checking Unrelated Types ===");
// Checking against unrelated type
// Note: This might not compile in newer Java versions due to
// compile-time type checking improvements
// boolean r4 = a1 instanceof Car; // Compilation error in newer Java
System.out.println("Cannot check if Animal is Car - " +
"they are unrelated types");
System.out.println("\n=== Polymorphism ===");
// Array of animals with polymorphic method calls
Animal[] animals = {new Lion(), new Frog(), new Lion()};
for (Animal animal : animals) {
animal.makeSound(); // Polymorphic call
// Access specific features based on actual type
if (animal instanceof Lion) {
((Lion) animal).hunt();
} else if (animal instanceof Frog) {
((Frog) animal).jump();
}
}
}
}Ответ: instanceof даёт безопасный downcasting: true, если dynamic type — сам указанный класс или его subclass, что снижает риск ClassCastException (RTTI).
3.7. Иерархия фигур на abstract class (Лекция 9, абстрактные классы)
Перепишите иерархию Shape на abstract class: в базе объявите Move(), Rotate(), Draw(), Increase() как abstract methods; Circle и Rectangle сделайте concrete subclasses.
Нажмите, чтобы увидеть решение
Идея: Abstract class удобен для «чисто абстрактных» понятий без new у базы и для принудительного контракта у всех concrete subclasses.
// Abstract base class
abstract class Shape {
// Data common to all shapes
protected int x, y; // coordinates
// Constructor
public Shape(int x, int y) {
this.x = x;
this.y = y;
}
// Abstract methods - behavior common to all shapes but implemented differently
abstract void Move(int dx, int dy);
abstract void Rotate(double angle);
abstract void Draw();
abstract void Increase(double factor);
}
// Concrete implementation for Circle
class Circle extends Shape {
private double radius;
public Circle(int x, int y, double radius) {
super(x, y);
this.radius = radius;
}
@Override
void Move(int dx, int dy) {
this.x += dx;
this.y += dy;
System.out.println("Circle moved to (" + x + ", " + y + ")");
}
@Override
void Rotate(double angle) {
// Circle rotation doesn't change appearance
System.out.println("Circle rotated by " + angle + " degrees " +
"(no visible change)");
}
@Override
void Draw() {
System.out.println("Drawing Circle at (" + x + ", " + y +
") with radius " + radius);
}
@Override
void Increase(double factor) {
radius *= factor;
System.out.println("Circle radius increased to " + radius);
}
}
// Concrete implementation for Rectangle
class Rectangle extends Shape {
private double width, height;
private double rotationAngle = 0;
public Rectangle(int x, int y, double width, double height) {
super(x, y);
this.width = width;
this.height = height;
}
@Override
void Move(int dx, int dy) {
this.x += dx;
this.y += dy;
System.out.println("Rectangle moved to (" + x + ", " + y + ")");
}
@Override
void Rotate(double angle) {
rotationAngle += angle;
rotationAngle %= 360; // Keep angle in [0, 360)
System.out.println("Rectangle rotated by " + angle + " degrees " +
"(total rotation: " + rotationAngle + "°)");
}
@Override
void Draw() {
System.out.println("Drawing Rectangle at (" + x + ", " + y +
") with width " + width + ", height " + height +
", rotation " + rotationAngle + "°");
}
@Override
void Increase(double factor) {
width *= factor;
height *= factor;
System.out.println("Rectangle size increased to " + width +
"x" + height);
}
}
// Demonstration
public class ShapeDemo {
public static void main(String[] args) {
// Cannot instantiate abstract class:
// Shape shape = new Shape(0, 0); // ERROR!
// Create array of shapes
Shape[] figures = new Shape[3];
figures[0] = new Circle(10, 20, 5.0);
figures[1] = new Rectangle(30, 40, 10.0, 20.0);
figures[2] = new Circle(50, 60, 7.5);
System.out.println("=== Drawing All Shapes ===");
for (Shape figure : figures) {
figure.Draw(); // Polymorphic call
}
System.out.println("\n=== Increasing All Shapes ===");
for (Shape figure : figures) {
figure.Increase(1.5); // Polymorphic call
}
System.out.println("\n=== Drawing After Increase ===");
for (Shape figure : figures) {
figure.Draw();
}
System.out.println("\n=== Moving and Rotating ===");
figures[0].Move(5, 5);
figures[0].Rotate(45);
figures[1].Move(-10, 10);
figures[1].Rotate(90);
System.out.println("\n=== Final State ===");
for (Shape figure : figures) {
figure.Draw();
}
System.out.println("\n=== Adding New Shape (Triangle) ===");
// The beauty: we can add Triangle without modifying existing code!
System.out.println("If we add a Triangle class, all the loops above");
System.out.println("will work without any modification!");
}
}Ответ: Abstract class здесь уместен, потому что:
- обобщённый
Shapeне должен создаваться черезnew— только конкретные фигуры; - общие данные (координаты) и операции (
Move,Rotate,Draw,Increase) одинаково актуальны для всех; - реализации различаются по типу фигуры;
- можно добавлять новые фигуры, не ломая общие циклы (Open/Closed Principle).
3.8. Иерархия Vehicle: абстрактный промежуточный уровень (Лекция 9, абстрактные классы)
Abstract class Vehicle с abstract method startEngine(); concrete class Motorbike и abstract class FlyingVehicle, показывающие, что производный класс тоже может оставаться абстрактным.
Нажмите, чтобы увидеть решение
Идея: Если производный класс не закрыл все abstract methods предков, он сам обязан быть abstract.
// Abstract base class
abstract class Vehicle {
// Common features for all vehicles
protected String color;
protected int numWheels;
public Vehicle(String color, int numWheels) {
this.color = color;
this.numWheels = numWheels;
}
// Abstract method - must be implemented by concrete subclasses
abstract void startEngine();
// Concrete method - available to all vehicles
void honk() {
System.out.println("Honk honk!");
}
void displayInfo() {
System.out.println("Color: " + color + ", Wheels: " + numWheels);
}
}
// Concrete class - implements all abstract methods
class Motorbike extends Vehicle {
public Motorbike(String color) {
super(color, 2);
}
@Override
void startEngine() {
System.out.println("Motorbike: Kick start! Vroom vroom!");
}
}
// Abstract class - doesn't implement abstract methods
abstract class FlyingVehicle extends Vehicle {
protected int maxAltitude;
public FlyingVehicle(String color, int numWheels, int maxAltitude) {
super(color, numWheels);
this.maxAltitude = maxAltitude;
}
// Doesn't implement startEngine() - remains abstract
// Adds new abstract method
abstract void takeOff();
// Concrete method specific to flying vehicles
void displayAltitude() {
System.out.println("Max altitude: " + maxAltitude + " meters");
}
}
// Concrete flying vehicle - must implement ALL abstract methods
class Airplane extends FlyingVehicle {
public Airplane(String color) {
super(color, 3, 12000);
}
@Override
void startEngine() {
System.out.println("Airplane: Starting jet engines... Engines running!");
}
@Override
void takeOff() {
System.out.println("Airplane: Accelerating down runway... Taking off!");
}
}
class Helicopter extends FlyingVehicle {
public Helicopter(String color) {
super(color, 0, 6000);
}
@Override
void startEngine() {
System.out.println("Helicopter: Starting rotors... Whop whop whop!");
}
@Override
void takeOff() {
System.out.println("Helicopter: Lifting off vertically!");
}
}
// Demonstration
public class VehicleDemo {
public static void main(String[] args) {
// Cannot instantiate abstract classes:
// Vehicle v = new Vehicle("red", 4); // ERROR!
// FlyingVehicle fv = new FlyingVehicle(...); // ERROR!
// Can instantiate concrete classes:
Motorbike bike = new Motorbike("Black");
Airplane plane = new Airplane("White");
Helicopter heli = new Helicopter("Green");
System.out.println("=== Motorbike ===");
bike.displayInfo();
bike.startEngine();
bike.honk();
System.out.println("\n=== Airplane ===");
plane.displayInfo();
plane.displayAltitude();
plane.startEngine();
plane.takeOff();
plane.honk();
System.out.println("\n=== Helicopter ===");
heli.displayInfo();
heli.displayAltitude();
heli.startEngine();
heli.takeOff();
System.out.println("\n=== Polymorphism with Vehicles ===");
Vehicle[] vehicles = {bike, plane, heli};
for (Vehicle vehicle : vehicles) {
vehicle.startEngine(); // Polymorphic call
}
System.out.println("\n=== Polymorphism with Flying Vehicles ===");
FlyingVehicle[] flyers = {plane, heli};
for (FlyingVehicle flyer : flyers) {
flyer.takeOff(); // Polymorphic call
}
}
}Ответ: Пример показывает: (1) concrete class Motorbike закрывает все abstract methods; (2) abstract class FlyingVehicle может расширять другой abstract class без реализации унаследованных abstract methods; (3) concrete Airplane/Helicopter обязаны закрыть всю «цепочку» abstract methods; (4) new для любого abstract class запрещён на любой глубине.
3.9. Пакеты и import (Лекция 9, пакеты)
Покажите объявление nested packages, импорт классов и использование fully-qualified name.
Нажмите, чтобы увидеть решение
Идея: Packages задают namespace, снижают коллизии имён и связаны с моделью доступа; структура каталогов должна совпадать с именем пакета.
Структура файлов:
project/
├── company/
│ └── department/
│ └── lab/
│ └── math/
│ ├── MathVector.java
│ └── Calculator.java
├── myPackage/
│ ├── PublicClass.java
│ └── PackageClass.java
└── Main.java
Файл MathVector.java:
package company.department.lab.math;
public class MathVector {
private double x, y;
public MathVector(double x, double y) {
this.x = x;
this.y = y;
}
public void display() {
System.out.println("Vector: (" + x + ", " + y + ")");
}
public double magnitude() {
return Math.sqrt(x * x + y * y);
}
}Файл Calculator.java:
package company.department.lab.math;
public class Calculator {
public static double add(double a, double b) {
return a + b;
}
public static double multiply(double a, double b) {
return a * b;
}
}Файл PublicClass.java:
package myPackage;
public class PublicClass {
public void publicMethod() {
System.out.println("This is a public class - accessible everywhere");
}
}Файл PackageClass.java:
package myPackage;
// No public modifier - package-private (default access)
class PackageClass {
void packageMethod() {
System.out.println("This is package-private - " +
"only accessible in myPackage");
}
}Файл Main.java:
// Three ways to use classes from other packages:
// 1. Import specific class
import company.department.lab.math.MathVector;
// 2. Import all classes from a package (import-on-demand)
import myPackage.*;
// 3. Use fully-qualified name (no import needed)
// company.department.lab.math.Calculator
public class Main {
public static void main(String[] args) {
System.out.println("=== Method 1: Imported Specific Class ===");
// Can use short name because we imported it
MathVector v1 = new MathVector(3.0, 4.0);
v1.display();
System.out.println("Magnitude: " + v1.magnitude());
System.out.println("\n=== Method 2: Import-on-Demand ===");
// Can use short name because we imported myPackage.*
PublicClass pc = new PublicClass();
pc.publicMethod();
// This won't work - PackageClass is not public:
// PackageClass pkc = new PackageClass(); // ERROR!
System.out.println("\n=== Method 3: Fully-Qualified Name ===");
// No import needed - use full name
double sum = company.department.lab.math.Calculator.add(10, 20);
double product = company.department.lab.math.Calculator.multiply(5, 6);
System.out.println("Sum: " + sum);
System.out.println("Product: " + product);
System.out.println("\n=== Standard Java Library ===");
// java.lang is automatically imported
String str = "Hello"; // String is from java.lang
System.out.println(str);
// Need to import other packages
java.util.ArrayList<Integer> list = new java.util.ArrayList<>();
list.add(1);
list.add(2);
System.out.println("List: " + list);
// Better to import:
// import java.util.ArrayList;
// or import java.util.*;
}
}Файл PackageDemo.java (в пакете myPackage):
package myPackage;
public class PackageDemo {
public static void main(String[] args) {
// Inside the same package, we can access package-private classes
PackageClass pkc = new PackageClass();
pkc.packageMethod(); // This works!
PublicClass pc = new PublicClass();
pc.publicMethod();
}
}Сборка и запуск:
# Compile (from project root)
javac company/department/lab/math/*.java
javac myPackage/*.java
javac Main.java
# Run
java MainВывод:
=== Method 1: Imported Specific Class ===
Vector: (3.0, 4.0)
Magnitude: 5.0
=== Method 2: Import-on-Demand ===
This is a public class - accessible everywhere
=== Method 3: Fully-Qualified Name ===
Sum: 30.0
Product: 30.0
=== Standard Java Library ===
Hello
List: [1, 2]
Ответ: Packages дают:
- Организацию: логические группы связанных классов.
- Пространства имён: меньше конфликтов имён (одно имя класса в разных пакетах).
- Контроль доступа: package-private классы не видны снаружи пакета.
- Именование: обратный DNS (
com.example.project) снижает глобальные коллизии.
3.10. UML: Book и Person (Туториал 9, ассоциация)
Постройте class diagram и соответствующий Java-код для association между Book и Person (авторы).
Нажмите, чтобы увидеть решение
Идея: Association — связь «использует / имеет»; направление стрелки показывает, кто о ком «знает».
Диаграмма UML:
Book Person
-name: String -name: String
-publisher: String -age: int
-authors: Person[] +Person(initialName: String)
+addAuthor(author: Person): void +printPerson(): void
+getAuthors(): Person[] +getName(): String
Стрелка: Book → Person (Book знает о Person, обратная ссылка в этом примере не задана).
Реализация на Java:
// Person.java
class Person {
private String name;
private int age;
public Person(String initialName) {
this.name = initialName;
this.age = 0;
}
public Person(String initialName, int initialAge) {
this.name = initialName;
this.age = initialAge;
}
public void printPerson() {
System.out.println(name + ", age: " + age + " years");
}
public String getName() {
return name;
}
}
// Book.java
class Book {
private String name;
private String publisher;
private Person[] authors;
private int authorCount = 0;
public Book(String name, String publisher, int maxAuthors) {
this.name = name;
this.publisher = publisher;
this.authors = new Person[maxAuthors];
}
public void addAuthor(Person author) {
if (authorCount < authors.length) {
authors[authorCount] = author;
authorCount++;
} else {
System.out.println("Cannot add more authors - array is full");
}
}
public Person[] getAuthors() {
// Return only the filled portion of the array
Person[] result = new Person[authorCount];
for (int i = 0; i < authorCount; i++) {
result[i] = authors[i];
}
return result;
}
public void displayBook() {
System.out.println("Book: " + name);
System.out.println("Publisher: " + publisher);
System.out.println("Authors:");
for (int i = 0; i < authorCount; i++) {
System.out.print(" - ");
authors[i].printPerson();
}
}
}
// Demo
public class AssociationDemo {
public static void main(String[] args) {
// Create persons (potential authors)
Person author1 = new Person("Alice Johnson", 45);
Person author2 = new Person("Bob Smith", 38);
Person author3 = new Person("Carol White", 52);
// Create a book
Book book = new Book("Java Programming Essentials",
"Tech Books Publishing", 3);
// Add authors to the book
book.addAuthor(author1);
book.addAuthor(author2);
book.addAuthor(author3);
// Display book information
book.displayBook();
System.out.println("\n=== Getting Authors ===");
Person[] bookAuthors = book.getAuthors();
for (Person author : bookAuthors) {
author.printPerson();
}
}
}Двунаправленная ассоциация (many-to-many):
Если и Book, и Person должны знать друг о друге:
class Person {
private String name;
private int age;
private Book[] books; // Books this person authored
private int bookCount = 0;
public Person(String initialName, int maxBooks) {
this.name = initialName;
this.age = 0;
this.books = new Book[maxBooks];
}
public void addBook(Book book) {
if (bookCount < books.length) {
books[bookCount] = book;
bookCount++;
}
}
public void printPerson() {
System.out.println(name + ", age: " + age + " years");
}
public String getName() {
return name;
}
public Book[] getBooks() {
Book[] result = new Book[bookCount];
for (int i = 0; i < bookCount; i++) {
result[i] = books[i];
}
return result;
}
}
class Book {
private String name;
private String publisher;
private Person[] authors;
private int authorCount = 0;
public Book(String name, String publisher, int maxAuthors) {
this.name = name;
this.publisher = publisher;
this.authors = new Person[maxAuthors];
}
public void addAuthor(Person author) {
if (authorCount < authors.length) {
authors[authorCount] = author;
authorCount++;
author.addBook(this); // Bidirectional link
}
}
public String getName() {
return name;
}
public Person[] getAuthors() {
Person[] result = new Person[authorCount];
for (int i = 0; i < authorCount; i++) {
result[i] = authors[i];
}
return result;
}
}UML для двунаправленной связи:
Book <--------*------*--------> Person
(Без стрелки — взаимное знание; * с двух сторон — many-to-many.)
Ответ: Association описывает совместную работу классов: направление — кто от кого зависит, cardinality — сколько экземпляров участвует в связи.
3.11. UML: композиция Building и Room (Туториал 9, композиция)
Покажите composition, где Room — часть Building и не существует как самостоятельная сущность вне здания.
Нажмите, чтобы увидеть решение
Идея: Composition — сильная принадлежность: при уничтожении целого (Building) уничтожаются и части (Rooms) в модели владения.
Диаграмма UML:
Building ◆-------- Room
-rooms: Room[]
~address: String
(Закрашенный ромб со стороны Building — composition.)
Реализация на Java:
class Building {
private String address;
private Room[] rooms;
// Inner class - Room cannot exist outside Building
class Room {
private int roomNumber;
private double area;
public Room(int roomNumber, double area) {
this.roomNumber = roomNumber;
this.area = area;
}
// Room can access Building's members
String getBuildingAddress() {
return Building.this.address;
}
void displayRoom() {
System.out.println(" Room " + roomNumber +
": " + area + " sq meters " +
"in building at " + getBuildingAddress());
}
}
public Building(String address, int numRooms) {
this.address = address;
this.rooms = new Room[numRooms];
// Create rooms - they are part of the building
for (int i = 0; i < numRooms; i++) {
rooms[i] = new Room(i + 1, 15.0 + i * 5);
}
}
public void displayBuilding() {
System.out.println("Building at: " + address);
System.out.println("Rooms:");
for (Room room : rooms) {
room.displayRoom();
}
}
// When building is demolished, rooms cease to exist
public void demolish() {
System.out.println("Demolishing building at " + address);
rooms = null; // Rooms are destroyed with the building
System.out.println("Building and all its rooms are destroyed");
}
}
public class CompositionDemo {
public static void main(String[] args) {
System.out.println("=== Creating Building ===");
Building building = new Building("123 Main Street", 4);
building.displayBuilding();
System.out.println("\n=== Demolishing Building ===");
building.demolish();
// After demolition, we cannot access rooms
// They were composed within the building
System.out.println("\n=== Key Point ===");
System.out.println("Rooms cannot exist without their building");
System.out.println("When the building is destroyed, rooms are destroyed too");
System.out.println("This is COMPOSITION");
}
}Вариант без inner class (всё ещё composition):
class Room {
private int roomNumber;
private double area;
private Building building; // Back-reference to owner
// Package-private constructor - only Building can create rooms
Room(int roomNumber, double area, Building building) {
this.roomNumber = roomNumber;
this.area = area;
this.building = building;
}
String getBuildingAddress() {
return building.getAddress();
}
void displayRoom() {
System.out.println(" Room " + roomNumber +
": " + area + " sq meters");
}
}
class Building {
private String address;
private Room[] rooms;
public Building(String address, int numRooms) {
this.address = address;
this.rooms = new Room[numRooms];
// Building creates and owns its rooms
for (int i = 0; i < numRooms; i++) {
rooms[i] = new Room(i + 1, 15.0 + i * 5, this);
}
}
String getAddress() {
return address;
}
public void displayBuilding() {
System.out.println("Building at: " + address);
System.out.println("Rooms:");
for (Room room : rooms) {
room.displayRoom();
}
}
}Ответ: Composition — «part-of» с сильным владением: (1) часть создаётся целым; (2) не живёт отдельно от модели целого; (3) при уничтожении целого исчезают части; (4) часто делают через inner class или строгую инкапсуляцию.
3.12. UML: агрегация Car и Wheel (Туториал 9, агрегация)
Покажите aggregation, где Wheel может существовать независимо от Car.
Нажмите, чтобы увидеть решение
Идея: Aggregation — слабое «has a»: часть может существовать независимо от целого.
Диаграмма UML:
Car ◇-------- Wheel
-wheels: Wheel[]
(Пустой ромб со стороны Car — aggregation.)
Реализация на Java:
// Wheel.java - Can exist independently
class Wheel {
private double diameter;
private String brand;
private String condition;
public Wheel(double diameter, String brand) {
this.diameter = diameter;
this.brand = brand;
this.condition = "new";
}
public void displayWheel() {
System.out.println(" " + brand + " wheel, " +
diameter + " inches, condition: " + condition);
}
public void wear() {
condition = "worn";
}
public String getBrand() {
return brand;
}
}
// Car.java - Has wheels but doesn't own them
class Car {
private String model;
private Wheel[] wheels;
public Car(String model) {
this.model = model;
this.wheels = new Wheel[4];
}
// Mount existing wheels
public void mountWheel(Wheel wheel, int position) {
if (position >= 0 && position < 4) {
wheels[position] = wheel;
System.out.println("Mounted wheel at position " + position);
}
}
// Remove a wheel - it still exists after removal
public Wheel removeWheel(int position) {
if (position >= 0 && position < 4) {
Wheel removed = wheels[position];
wheels[position] = null;
System.out.println("Removed wheel from position " + position);
return removed;
}
return null;
}
public void displayCar() {
System.out.println("Car: " + model);
System.out.println("Wheels:");
for (int i = 0; i < 4; i++) {
System.out.print(" Position " + i + ": ");
if (wheels[i] != null) {
wheels[i].displayWheel();
} else {
System.out.println("No wheel");
}
}
}
}
public class AggregationDemo {
public static void main(String[] args) {
System.out.println("=== Creating Wheels (Independent) ===");
Wheel wheel1 = new Wheel(17, "Michelin");
Wheel wheel2 = new Wheel(17, "Bridgestone");
Wheel wheel3 = new Wheel(17, "Michelin");
Wheel wheel4 = new Wheel(17, "Bridgestone");
wheel1.displayWheel();
wheel2.displayWheel();
System.out.println("\n=== Creating Car ===");
Car car1 = new Car("Toyota Camry");
System.out.println("\n=== Mounting Wheels ===");
car1.mountWheel(wheel1, 0);
car1.mountWheel(wheel2, 1);
car1.mountWheel(wheel3, 2);
car1.mountWheel(wheel4, 3);
System.out.println();
car1.displayCar();
System.out.println("\n=== Removing a Wheel ===");
Wheel removedWheel = car1.removeWheel(0);
car1.displayCar();
System.out.println("\n=== Removed Wheel Still Exists ===");
System.out.println("The removed wheel:");
removedWheel.displayWheel();
System.out.println("It can be mounted on another car!");
System.out.println("\n=== Creating Second Car ===");
Car car2 = new Car("Honda Civic");
car2.mountWheel(removedWheel, 0);
System.out.println();
car2.displayCar();
System.out.println("\n=== Spare Wheels ===");
Wheel spareWheel1 = new Wheel(17, "Goodyear");
Wheel spareWheel2 = new Wheel(17, "Pirelli");
System.out.println("These wheels exist but aren't mounted on any car:");
spareWheel1.displayWheel();
spareWheel2.displayWheel();
System.out.println("\n=== Key Point ===");
System.out.println("Wheels can exist without a car");
System.out.println("Wheels can be removed and installed on different cars");
System.out.println("If a car is destroyed, its wheels still exist");
System.out.println("This is AGGREGATION");
}
}Вывод:
=== Creating Wheels (Independent) ===
Michelin wheel, 17.0 inches, condition: new
Bridgestone wheel, 17.0 inches, condition: new
=== Creating Car ===
=== Mounting Wheels ===
Mounted wheel at position 0
Mounted wheel at position 1
Mounted wheel at position 2
Mounted wheel at position 3
Car: Toyota Camry
Wheels:
Position 0: Michelin wheel, 17.0 inches, condition: new
Position 1: Bridgestone wheel, 17.0 inches, condition: new
Position 2: Michelin wheel, 17.0 inches, condition: new
Position 3: Bridgestone wheel, 17.0 inches, condition: new
=== Removing a Wheel ===
Removed wheel from position 0
Car: Toyota Camry
Wheels:
Position 0: No wheel
Position 1: Bridgestone wheel, 17.0 inches, condition: new
Position 2: Michelin wheel, 17.0 inches, condition: new
Position 3: Bridgestone wheel, 17.0 inches, condition: new
=== Removed Wheel Still Exists ===
The removed wheel:
Michelin wheel, 17.0 inches, condition: new
It can be mounted on another car!
=== Creating Second Car ===
Mounted wheel at position 0
Car: Honda Civic
Wheels:
Position 0: Michelin wheel, 17.0 inches, condition: new
Position 1: No wheel
Position 2: No wheel
Position 3: No wheel
=== Spare Wheels ===
These wheels exist but aren't mounted on any car:
Goodyear wheel, 17.0 inches, condition: new
Pirelli wheel, 17.0 inches, condition: new
=== Key Point ===
Wheels can exist without a car
Wheels can be removed and installed on different cars
If a car is destroyed, its wheels still exist
This is AGGREGATION
Ответ: Aggregation отличается от composition: часть (Wheel) существует сама по себе; может быть создана до «целого»; может переезжать между Car; при уничтожении Car колёса остаются; жизненный цикл части не совпадает с целым.