Observable.addObserver: примеры (JAVA)

Взаимодействие Observer и Observable в Java
Раздел: Наблюдение
Observable.addObserver(Observer o): void

Описание метода

Метод addObserver принадлежит классу java.util.Observable и имеет сигнатуру public void addObserver(Observer o). Его задача - зарегистрировать объект, реализующий интерфейс java.util.Observer, в списке наблюдателей субъекта. После регистрации этот объект будет получать уведомления при вызове notifyObservers() при условии, что состояние субъекта помечено как изменённое через setChanged().

Аргументы и поведение:

  • Observer o - обязательный аргумент. При передаче null метод выбрасывает NullPointerException.
  • Метод не возвращает значения (тип void).
  • Добавление производится синхронизированно. Если тот же объект уже присутствует в списке, дубликат не добавляется.
  • При вызове notifyObservers(arg) каждый зарегистрированный наблюдатель получает вызов update(Observable o, Object arg), если ранее вызван setChanged().

Дополнительные замечания:

  • Класс Observable и интерфейс Observer считались устаревшими и помечены как @Deprecated начиная с Java 9. Рекомендованы более современные средства (см. раздел альтернатив).
  • Внутренняя реализация использует синхронизируемую коллекцию; это упрощает некоторые многопоточные сценарии, но накладывает ограничения на гибкость и расширяемость.

Короткие примеры

Пример 1. Базовая регистрация и уведомление.

import java.util.Observable;
import java.util.Observer;

class MyObservable extends Observable {}

class MyObserver implements Observer {
    public void update(Observable o, Object arg) {
        System.out.println("Получено: " + arg);
    }
}

public class Main {
    public static void main(String[] args) {
        MyObservable subj = new MyObservable();
        MyObserver obs = new MyObserver();
        subj.addObserver(obs);
        subj.setChanged();
        subj.notifyObservers("Привет");
    }
}
Получено: Привет

Пример 2. Попытка добавить дважды тот же объект (дубликат не добавляется).

// Используются те же классы MyObservable и MyObserver
MyObservable subj = new MyObservable();
MyObserver obs = new MyObserver();
subj.addObserver(obs);
subj.addObserver(obs); // повторная регистрация
subj.setChanged();
subj.notifyObservers("Тест");
Получено: Тест

Пример 3. Передача null приводит к исключению.

MyObservable subj = new MyObservable();
subj.addObserver(null);
Exception in thread "main" java.lang.NullPointerException
	at java.base/java.util.Observable.addObserver(Observable.java:...)
	... 

Пример 4. Забыт вызов setChanged - уведомлений не будет.

MyObservable subj = new MyObservable();
MyObserver obs = new MyObserver();
subj.addObserver(obs);
// subj.setChanged(); // забыто
subj.notifyObservers("Данные");
(нет вывода)

Альтернативы в Java

  • PropertyChangeSupport / PropertyChangeListener (java.beans) - позволяет подписываться на изменения именованных свойств с передачей старого и нового значений. Часто предпочтительнее для изменения состояния объектов модели.
  • java.util.concurrent.Flow (и интерфейсы Publisher/Subscriber) - современный стандарт реактивной передачи данных в Java 9. Поддерживает управление давлением (backpressure) и асинхронность.
  • Событийная модель JavaBeans и слушатели событий - набор конвенций для GUI и приложений, подходящая при необходимости типизированных событий.
  • RxJava, Reactor - реактивные библиотеки с богатым набором операторов; предпочтительны для сложных асинхронных потоков данных.
  • JavaFX ObservableValue и связанные API - удобны для UI-сценариев и биндинга свойств.

Выбор зависит от требований: для простых локальных уведомлений достаточно PropertyChangeSupport; для реактивной асинхронной передачи лучше Flow или сторонние реактивные библиотеки.

Альтернативы в других языках

Ниже приведены краткие аналоги observer-паттерна в различных языках с примерами.

PHP (SPL)

// PHP: SplSubject и SplObserver
class Subject implements SplSubject {
    private $observers;
    private $state;

    public function __construct(){ $this->observers = new SplObjectStorage(); }
    public function attach(SplObserver $o){ $this->observers->attach($o); }
    public function detach(SplObserver $o){ $this->observers->detach($o); }
    public function notify(){
        foreach ($this->observers as $o) $o->update($this);
    }
    public function setState($s){ $this->state = $s; }
    public function getState(){ return $this->state; }
}

class Observer implements SplObserver {
    public function update(SplSubject $s){ echo "PHP получено: " . $s->getState() . "\n"; }
}

$sub = new Subject();
$obs = new Observer();
$sub->attach($obs);
$sub->setState('ok');
$sub->notify();
PHP получено: ok

JavaScript (Node.js EventEmitter)

const EventEmitter = require('events');
const ee = new EventEmitter();
ee.on('msg', data => console.log('JS:', data));
ee.emit('msg', 'привет');
JS: привет

Python (простая реализация)

class Observable:
    def __init__(self):
        self._obs = []
    def add_observer(self, o):
        self._obs.append(o)
    def notify(self, arg=None):
        for o in list(self._obs):
            o.update(self, arg)

class Observer:
    def update(self, subj, arg):
        print('Py received:', arg)

s = Observable()
o = Observer()
s.add_observer(o)
s.notify('hello')
Py received: hello

C# (события / IObservable)

// C# события
using System;
class Subject {
    public event Action Updated;
    public void Change(string v){ Updated?.Invoke(v); }
}

class Program{
    static void Main(){
        var s = new Subject();
        s.Updated += v => Console.WriteLine("C#: " + v);
        s.Change("ok");
    }
}
C#: ok

Go (каналы)

package main
import "fmt"

func main(){
    ch := make(chan string)
    go func(){
        for v := range ch { fmt.Println("Go:", v) }
    }()
    ch <- "hello"
    close(ch)
}
Go: hello

Kotlin (Delegates.observable)

import kotlin.properties.Delegates

var prop: String by Delegates.observable("") { _, old, new ->
    println("Kotlin: $old -> $new")
}
fun main(){ prop = "value" }
Kotlin:  -> value

Lua (колбэки)

local observers = {}
local function addObserver(o) table.insert(observers, o) end
local function notify(v) for _,o in ipairs(observers) do o(v) end end
addObserver(function(x) print('Lua:', x) end)
notify('ok')
Lua: ok

SQL

В реляционных СУБД аналог - триггеры, которые реагируют на изменения данных. Отличие: триггеры исполняются внутри СУБД и не предоставляют прямую модель подписки в приложении.

Типичные ошибки

  • Передача null. addObserver(null) приводит к NullPointerException. Пример и результат приведены в разделе примеров.
  • Ожидание уведомления без setChanged. notifyObservers без предварительного setChanged не оповестит наблюдателей. Частая причина недоразумений.
  • Изменение коллекции наблюдателей во время уведомления. Хотя Observable использует синхронизируемую коллекцию, логика обновления и модификации списка внутри обработчиков может вести к неожиданному поведению; предпочтительнее копировать список перед итерацией при сложных сценариях.
  • Нарушение инкапсуляции. Наследование от Observable и доступ к защищённым полям может привести к хрупкому коду.
  • Использование устаревшего API. Observable помечен как deprecated; его применение в новых проектах нежелательно.

Пример: забытый setChanged.

MyObservable subj = new MyObservable();
subj.addObserver(new MyObserver());
// subj.setChanged(); // забыто
subj.notifyObservers("X");
(нет вывода) - наблюдатель не вызван

Изменения и статус

Класс java.util.Observable и интерфейс java.util.Observer были помечены как @Deprecated в спецификации Java 9. Причины deprecation: ограниченная гибкость API, проблемы с поддержкой расширенных сценариев (например, управление подпиской, конкурентный доступ, типизация событий) и наличие более современных механизмов асинхронной работы и реактивного программирования.

За пределами пометки об устаревании изменений в поведении метода addObserver в самых новых релизах не происходило. Рекомендуется применять Flow API или сторонние реактивные библиотеки для новых разработок.

Расширенные примеры

1) Наблюдатель с фильтрацией событий на стороне субъекта.

Пример java
import java.util.*;

class FilteredObservable extends Observable {
    public void notifyIfChanged(Object arg, Observer filter) {
        setChanged();
        for (Observer o : Collections.list(super.countObservers()>0?null:new Vector())) {}
    }
}

// Примечание: демонстрация идеи; в реальном коде лучше реализовать собственные структуры
(идея: фильтрация на стороне субъекта требует собственной реализации)

2) Асинхронное уведомление через ExecutorService.

Пример java
import java.util.*;
import java.util.concurrent.*;

class AsyncObservable extends Observable {
    private final ExecutorService ex = Executors.newSingleThreadExecutor();
    public void notifyAsync(final Object arg) {
        setChanged();
        final Observer[] arr;
        synchronized(this) {
            arr = observers.toArray(new Observer[0]);
        }
        for (Observer o : arr) {
            ex.submit(() -> o.update(this, arg));
        }
    }
}

// Использование: создаются наблюдатели, вызывается notifyAsync
(вызовы update выполняются в другом потоке; вывод в консоль появится асинхронно)

3) Слабые ссылки для предотвращения утечек памяти.

Пример java
import java.lang.ref.WeakReference;
import java.util.*;

class WeakObservable {
    private final List> list = new ArrayList<>();
    public void addObserver(Observer o){ list.add(new WeakReference<>(o)); }
    public void notifyObservers(Object arg){
        Iterator> it = list.iterator();
        while (it.hasNext()){
            Observer o = it.next().get();
            if (o == null) it.remove(); else o.update(null, arg);
        }
    }
}
(наблюдатели, не удерживаемые другими ссылками, будут автоматически убираться сборщиком мусора)

4) Мост между Observable и Flow.Publisher (упрощённый пример концепции).

Пример java
// Идея: обёртка, которая при notify вызывает submit в SubmissionPublisher
// Полная реализация требует обработки подписок и backpressure
(в проектах с Java 9+ рекомендуется использовать Flow/SubmissionPublisher напрямую)

5) Удаление наблюдателя внутри update: демонстрация безопасного удаления.

Пример java
class SelfRemovingObserver implements Observer {
    private final Observable subj;
    public SelfRemovingObserver(Observable s){ subj = s; }
    public void update(Observable o, Object arg){
        System.out.println("Удаляюсь после получения: " + arg);
        subj.deleteObserver(this);
    }
}

// При последовательных уведомлениях этот наблюдатель получит только первое сообщение
Удаляюсь после получения: первый
(далее не вызывается)

джава Observable.addObserver function comments

En
Observable.addObserver Добавляет наблюдателя