Skip to content
IRC-CodingIRC-Coding
Static методыInstance атрибутыСвязи классовGenericsUML

OOP Классы: Static vs Instance методы и атрибуты

OOP классы: static и instance элементы, связи классов, Generics. UML диаграммы, различия и type-safe контейнеры с примерами.

S

schutzgeist

8 min read
OOP Классы: Static vs Instance методы и атрибуты

ООП классы: статические и экземплярные методы, атрибуты и связи

В этой статье разбираем структуру классов в объектно-ориентированном программировании, включая статические и экземплярные элементы, связи между классами и обобщённые типы с практическими примерами.

Коротко о главном

Класс состоит из атрибутов, методов, уровней доступа и контрактов. Статические элементы относятся к самому классу, экземплярные — к отдельным объектам. Обобщённые типы повышают типобезопасность и переиспользуемость кода.

Определение

Класс определяет структуру и поведение через атрибуты, методы, уровни доступа, конструкторы, инварианты и контракты.

Статические и экземплярные элементы:

Статические элементы (уровень класса)

  • Статические атрибуты: принадлежат классу, а не объектам
  • Статические методы: вызываются без создания объекта
  • Применение: счётчики, кэши, фабрики, константы
  • Доступ: через имя класса, не через объект

Экземплярные элементы (уровень объекта)

  • Экземплярные атрибуты: каждый объект хранит свою копию
  • Экземплярные методы: работают с состоянием объекта
  • Применение: данные и поведение, специфичные для объекта
  • Доступ: только через экземпляр объекта

Связи между классами (UML):

  • Ассоциация: простая связь между классами
  • Агрегация: отношение “имеет” с независимыми частями
  • Композиция: отношение “является частью” с зависимыми частями
  • Специализация: подкласс наследует базовый класс

Обобщённые типы:

  • Типобезопасность: проверка типов на этапе компиляции
  • Переиспользуемость: один код для разных типов
  • Контейнеры: List<T>, Map<K,V>, std::vector<T>

Ключевые моменты

  • Статические атрибуты: принадлежат классу, общее значение для всех объектов
  • Экземплярные атрибуты: у каждого объекта своя копия
  • Статические методы: вызываются без объекта, нет указателя this
  • Экземплярные методы: работают с состоянием объекта через указатель this
  • Связи между классами: ассоциация, агрегация, композиция, наследование
  • Нотация UML: разные символы для разных типов связей
  • Обобщённые типы: шаблоны для типобезопасных контейнеров
  • Практическое применение: фундамент объектно-ориентированной разработки

Основные компоненты

  1. Статические атрибуты: переменные класса для совместного состояния
  2. Экземплярные атрибуты: переменные объекта для индивидуального состояния
  3. Статические методы: методы класса без привязки к объекту
  4. Экземплярные методы: методы объекта с доступом к состоянию
  5. Конструкторы: инициализация объекта
  6. Связи: структурные связи между классами
  7. Обобщённые типы: классы и методы с параметрами типа
  8. 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: сложность при параллельном доступе
  • Гибкость: отсутствие полиморфизма
  • Инициализация: сложные зависимости

Частые вопросы на экзамене

  1. В чём отличие между статическими и экземплярными атрибутами? Статические атрибуты принадлежат классу (одна копия), экземплярные принадлежат объекту (по копии на объект).

  2. Могут ли статические методы получать доступ к атрибутам экземпляра? Нет, так как они не имеют указателя this и не знают, какому объекту принадлежат.

  3. Объясните агрегацию и композицию! Агрегация: части могут существовать без целого, композиция: части существуют только вместе с целым.

  4. Почему статические атрибуты не являются генерическими? Они принадлежат классу, а не экземпляру, поэтому существует только одна версия на класс.

Основные источники

  1. https://docs.oracle.com/javase/tutorial/java/javaOO/classvars.html
  2. https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/static-classes-and-static-class-members
  3. https://www.uml-diagrams.org/class-diagrams.html

Продолжение пути обучения OOP

Все статьи по OOP теперь завершены. Вернитесь к первой статье: Объектно-ориентированное программирование OOP основы.

Назад к блогу
Share:

Nächster Artikel in Разработка программного обеспечения

Weiterlesen
OOP Наследование: основы, Inheritance и Polymorphism

Похожие статьи