ООП классы: статические и экземплярные методы, атрибуты и связи
В этой статье разбираем структуру классов в объектно-ориентированном программировании, включая статические и экземплярные элементы, связи между классами и обобщённые типы с практическими примерами.
Коротко о главном
Класс состоит из атрибутов, методов, уровней доступа и контрактов. Статические элементы относятся к самому классу, экземплярные — к отдельным объектам. Обобщённые типы повышают типобезопасность и переиспользуемость кода.
Определение
Класс определяет структуру и поведение через атрибуты, методы, уровни доступа, конструкторы, инварианты и контракты.
Статические и экземплярные элементы:
Статические элементы (уровень класса)
- Статические атрибуты: принадлежат классу, а не объектам
- Статические методы: вызываются без создания объекта
- Применение: счётчики, кэши, фабрики, константы
- Доступ: через имя класса, не через объект
Экземплярные элементы (уровень объекта)
- Экземплярные атрибуты: каждый объект хранит свою копию
- Экземплярные методы: работают с состоянием объекта
- Применение: данные и поведение, специфичные для объекта
- Доступ: только через экземпляр объекта
Связи между классами (UML):
- Ассоциация: простая связь между классами
- Агрегация: отношение “имеет” с независимыми частями
- Композиция: отношение “является частью” с зависимыми частями
- Специализация: подкласс наследует базовый класс
Обобщённые типы:
- Типобезопасность: проверка типов на этапе компиляции
- Переиспользуемость: один код для разных типов
- Контейнеры:
List<T>,Map<K,V>,std::vector<T>
Ключевые моменты
- Статические атрибуты: принадлежат классу, общее значение для всех объектов
- Экземплярные атрибуты: у каждого объекта своя копия
- Статические методы: вызываются без объекта, нет указателя this
- Экземплярные методы: работают с состоянием объекта через указатель this
- Связи между классами: ассоциация, агрегация, композиция, наследование
- Нотация UML: разные символы для разных типов связей
- Обобщённые типы: шаблоны для типобезопасных контейнеров
- Практическое применение: фундамент объектно-ориентированной разработки
Основные компоненты
- Статические атрибуты: переменные класса для совместного состояния
- Экземплярные атрибуты: переменные объекта для индивидуального состояния
- Статические методы: методы класса без привязки к объекту
- Экземплярные методы: методы объекта с доступом к состоянию
- Конструкторы: инициализация объекта
- Связи: структурные связи между классами
- Обобщённые типы: классы и методы с параметрами типа
- UML: графическая нотация для проектирования классов
Практические примеры
1. Статические и экземплярные элементы в Java
public class Mitarbeiter {
// Статические атрибуты (принадлежат классу)
private static int anzahlMitarbeiter = 0;
private static final String FIRMA = "TechCorp";
private static double mindestgehalt = 2000.0;
// Экземплярные атрибуты (принадлежат объекту)
private int mitarbeiterId;
private String name;
private double gehalt;
// Статический блок инициализации
static {
System.out.println("Mitarbeiter-Klasse wird geladen");
anzahlMitarbeiter = 0;
}
// Конструктор (инициализация экземпляра)
public Mitarbeiter(String name, double gehalt) {
this.mitarbeiterId = ++anzahlMitarbeiter;
this.name = name;
this.gehalt = Math.max(gehalt, mindestgehalt);
System.out.println("Mitarbeiter " + name + " erstellt (ID: " + mitarbeiterId + ")");
}
// Статический метод (можно вызвать без объекта)
public static int getAnzahlMitarbeiter() {
return anzahlMitarbeiter;
}
public static String getFirma() {
return FIRMA;
}
public static void setMindestgehalt(double mindestgehalt) {
if (mindestgehalt > 0) {
Mitarbeiter.mindestgehalt = mindestgehalt;
}
}
// Экземплярный метод (требует объект)
public void erhoeheGehalt(double prozent) {
this.gehalt *= (1 + prozent / 100);
System.out.println(name + "s Gehalt erhöht auf " + gehalt);
}
public void anzeigen() {
System.out.println("ID: " + mitarbeiterId + ", Name: " + name +
", Gehalt: " + gehalt + ", Firma: " + FIRMA);
}
// Getter/Setter для экземплярных атрибутов
public String getName() {
return name;
}
public double getGehalt() {
return gehalt;
}
}
// Использование класса
public class MitarbeiterDemo {
public static void main(String[] args) {
// Вызов статических методов (без объекта)
System.out.println("Anzahl Mitarbeiter: " + Mitarbeiter.getAnzahlMitarbeiter());
System.out.println("Firma: " + Mitarbeiter.getFirma());
Mitarbeiter.setMindestgehalt(2500.0);
// Создание объектов (экземпляры)
Mitarbeiter alice = new Mitarbeiter("Alice", 3000.0);
Mitarbeiter bob = new Mitarbeiter("Bob", 2800.0);
// Вызов экземплярных методов
alice.erhoeheGehalt(5.0);
bob.anzeigen();
// Статический метод после создания объектов
System.out.println("Anzahl Mitarbeiter: " + Mitarbeiter.getAnzahlMitarbeiter());
// Ошибка: this недоступен в статических методах
// public static void fehlerhafteMethode() {
// System.out.println(this.name); // Ошибка: нельзя использовать this
// }
}
}
2. Связи между классами с примерами UML
// Ассоциация: Dozent преподаёт Kurse
public class Dozent {
private String name;
private List<Kurs> unterrichteteKurse = new ArrayList<>();
public Dozent(String name) {
this.name = name;
}
public void addKurs(Kurs kurs) {
unterrichteteKurse.add(kurs);
kurs.setDozent(this); // Обратная ссылка
}
public void zeigeKurse() {
System.out.println(name + " unterrichtet:");
for (Kurs kurs : unterrichteteKurse) {
System.out.println(" - " + kurs.getTitel());
}
}
}
public class Kurs {
private String titel;
private Dozent dozent; // Обратная ссылка
public Kurs(String titel) {
this.titel = titel;
}
public void setDozent(Dozent dozent) {
this.dozent = dozent;
}
public String getTitel() {
return titel;
}
}
// Агрегация: Abteilung имеет Mitarbeiter (они могут существовать отдельно)
public class Abteilung {
private String name;
private List<Mitarbeiter> mitarbeiter = new ArrayList<>();
public Abteilung(String name) {
this.name = name;
}
public void addMitarbeiter(Mitarbeiter mitarbeiter) {
this.mitarbeiter.add(mitarbeiter);
}
public void removeMitarbeiter(Mitarbeiter mitarbeiter) {
this.mitarbeiter.remove(mitarbeiter);
// Mitarbeiter существует дальше
}
}
// Композиция: Bestellung содержит Bestellposition (существуют только в её контексте)
public class Bestellung {
private String bestellId;
private List<Bestellposition> positionen = new ArrayList<>();
public Bestellung(String bestellId) {
this.bestellId = bestellId;
}
public void addPosition(String produkt, int menge, double preis) {
Bestellposition position = new Bestellposition(produkt, menge, preis);
positionen.add(position);
}
public double berechneGesamtbetrag() {
return positionen.stream()
.mapToDouble(Bestellposition::getGesamtpreis)
.sum();
}
// Внутренний класс для композиции
private class Bestellposition {
private String produkt;
private int menge;
private double einzelpreis;
public Bestellposition(String produkt, int menge, double einzelpreis) {
this.produkt = produkt;
this.menge = menge;
this.einzelpreis = einzelpreis;
}
public double getGesamtpreis() {
return menge * einzelpreis;
}
}
}
3. Generics со статическими элементами
// Генерическая класс со статическими элементами
public class Container<T> {
// Статические атрибуты (не являются генерическими!)
private static int anzahlContainer = 0;
private static final String VERSION = "1.0";
// Атрибуты экземпляра (генерические)
private T inhalt;
private int id;
public Container(T inhalt) {
this.inhalt = inhalt;
this.id = ++anzahlContainer;
}
// Статический метод (не может получать доступ к T)
public static int getAnzahlContainer() {
return anzahlContainer;
}
public static String getVersion() {
return VERSION;
}
// Метод экземпляра (может получать доступ к T)
public T getInhalt() {
return inhalt;
}
public void setInhalt(T inhalt) {
this.inhalt = inhalt;
}
public void anzeigen() {
System.out.println("Container #" + id + ": " +
(inhalt != null ? inhalt.toString() : "leer"));
}
// Генерический метод (статический)
public static <U> Container<U> create(U inhalt) {
return new Container<>(inhalt);
}
}
// Использование
public class ContainerDemo {
public static void main(String[] args) {
// Вызов статических методов
System.out.println("Container-Version: " + Container.getVersion());
// Различные типы контейнеров
Container<String> stringContainer = new Container<>("Hallo");
Container<Integer> intContainer = new Container<>(42);
Container<Double> doubleContainer = Container.create(3.14);
stringContainer.anzeigen();
intContainer.anzeigen();
doubleContainer.anzeigen();
System.out.println("Anzahl Container: " + Container.getAnzahlContainer());
// Ошибка: статические атрибуты не являются генерическими
// Container<String>.getAnzahlContainer(); // Syntax-Fehler
}
}
4. Factory Pattern со статическими методами
public class FahrzeugFactory {
// Статические фабричные методы
public static Fahrzeug erstellePKW(String marke, int leistung) {
return new PKW(marke, leistung, 4);
}
public static Fahrzeug erstelleMotorrad(String marke, int leistung) {
return new Motorrad(marke, leistung, false);
}
public static Fahrzeug erstelleLKW(String marke, int leistung, double ladung) {
return new LKW(marke, leistung, ladung);
}
// Статический метод с проверкой
public static Fahrzeug erstelleFahrzeug(String typ, String marke, int leistung) {
switch (typ.toLowerCase()) {
case "pkw":
return erstellePKW(marke, leistung);
case "motorrad":
return erstelleMotorrad(marke, leistung);
case "lkw":
return erstelleLKW(marke, leistung, 1000.0);
default:
throw new IllegalArgumentException("Unbekannter Fahrzeugtyp: " + typ);
}
}
}
// Абстрактный базовый класс
abstract class Fahrzeug {
protected String marke;
protected int leistung;
public Fahrzeug(String marke, int leistung) {
this.marke = marke;
this.leistung = leistung;
}
public abstract void anzeigen();
}
// Конкретные классы
class PKW extends Fahrzeug {
private int anzahlTueren;
public PKW(String marke, int leistung, int anzahlTueren) {
super(marke, leistung);
this.anzahlTueren = anzahlTueren;
}
@Override
public void anzeigen() {
System.out.println("PKW: " + marke + ", " + leistung + " PS, " + anzahlTueren + " Türen");
}
}
class Motorrad extends Fahrzeug {
private boolean hatSeitenwagen;
public Motorrad(String marke, int leistung, boolean hatSeitenwagen) {
super(marke, leistung);
this.hatSeitenwagen = hatSeitenwagen;
}
@Override
public void anzeigen() {
System.out.println("Motorrad: " + marke + ", " + leistung + " PS, " +
(hatSeitenwagen ? "mit" : "ohne") + " Seitenwagen");
}
}
class LKW extends Fahrzeug {
private double ladung;
public LKW(String marke, int leistung, double ladung) {
super(marke, leistung);
this.ladung = ladung;
}
@Override
public void anzeigen() {
System.out.println("LKW: " + marke + ", " + leistung + " PS, " + ladung + " kg Ladung");
}
}
// Использование фабрики
public class FactoryDemo {
public static void main(String[] args) {
// Использование статических фабричных методов
Fahrzeug golf = FahrzeugFactory.erstellePKW("Volkswagen", 110);
Fahrzeug harley = FahrzeugFactory.erstelleMotorrad("Harley", 80);
Fahrzeug scania = FahrzeugFactory.erstelleLKW("Scania", 500, 20000.0);
golf.anzeigen();
harley.anzeigen();
scania.anzeigen();
// Динамическое создание
Fahrzeug bmw = FahrzeugFactory.erstelleFahrzeug("pkw", "BMW", 150);
bmw.anzeigen();
}
}
UML-нотация для элементов класса
Класс со статическими и экземплярными элементами
+---------------------------+
| Mitarbeiter |
+---------------------------+
| - anzahlMitarbeiter: int | <<static>>
| - FIRMA: String | <<static>>
| - mitarbeiterId: int |
| - name: String |
| - gehalt: double |
+---------------------------+
| + getAnzahlMitarbeiter(): int | <<static>>
| + setMindestgehalt(double): void | <<static>>
| + erhoeheGehalt(double): void |
| + anzeigen(): void |
+---------------------------+
Связи в UML
Dozent 1..* --* Kurs (Assoziation)
Abteilung 1 --o* Mitarbeiter (Aggregation)
Bestellung 1 --* Bestellposition (Komposition)
Fahrzeug <|-- PKW (Vererbung)
Статические против экземплярных элементов: помощь в выборе
Когда использовать статические элементы?
Статические атрибуты:
- Счётчики для всех экземпляров
- Константы для класса
- Общие ресурсы (подключение к БД)
- Кэши на уровне класса
Статические методы:
- Factory-методы для создания объектов
- Утилиты без состояния
- Методы конвертации
- Методы валидации
Когда использовать экземплярные элементы?
Атрибуты экземпляра:
- Данные, специфичные для объекта
- Состояние, которое изменяется
- Конфигурация на объект
Методы экземпляра:
- Методы, которые работают с состоянием объекта
- Поведение, зависящее от данных экземпляра
- Методы, требующие указателя this
Преимущества и недостатки
Преимущества статических элементов
- Эффективность памяти: одна копия для всех объектов
- Простой доступ: вызов без создания экземпляра
- Общее состояние: одинаковое для всех объектов
- Factory Pattern: простое создание объектов
Недостатки
- Глобальное состояние: затрудняет тестирование
- Thread-Safety: сложность при параллельном доступе
- Гибкость: отсутствие полиморфизма
- Инициализация: сложные зависимости
Частые вопросы на экзамене
-
В чём отличие между статическими и экземплярными атрибутами? Статические атрибуты принадлежат классу (одна копия), экземплярные принадлежат объекту (по копии на объект).
-
Могут ли статические методы получать доступ к атрибутам экземпляра? Нет, так как они не имеют указателя this и не знают, какому объекту принадлежат.
-
Объясните агрегацию и композицию! Агрегация: части могут существовать без целого, композиция: части существуют только вместе с целым.
-
Почему статические атрибуты не являются генерическими? Они принадлежат классу, а не экземпляру, поэтому существует только одна версия на класс.
Основные источники
- https://docs.oracle.com/javase/tutorial/java/javaOO/classvars.html
- https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/static-classes-and-static-class-members
- https://www.uml-diagrams.org/class-diagrams.html
Продолжение пути обучения OOP
Все статьи по OOP теперь завершены. Вернитесь к первой статье: Объектно-ориентированное программирование OOP основы.



