Java-делегати?


194

Чи має мова Java функції делегата, подібно до того, як C # має підтримку делегатів?


20
@Suma Як це може бути дублікатом, якщо згадане вами питання було опубліковане через рік після цього?
Ані

1
У Java 8 є така функція, як делегати. Це називається лямбда.
tbodt

4
@tbodt щоб бути правильнішим, у Java 8 є така функція, як делегати. Це називається функціональними інтерфейсами . Лямбди - це один із способів створення таких екземплярів-делегатов (анонімно).
nawfal

3
Відповіді нижче - від Pre-Java 8. Post Java 8 див. Відповіді в цій темі: stackoverflow.com/questions/20311779/…
Michael Lloyd Lee mlk

@nawfal, +1, але щоб бути ще правильнішим, у Java 7 і нижче вже є така функція, як делегати. Це називається простими інтерфейсами.
Pacerier

Відповіді:


152

Не дуже, ні.

Можливо, ви зможете досягти того ж ефекту, використовуючи відображення, щоб отримати об'єкти Method, до яких ви можете потім викликати, а інший спосіб - створити інтерфейс з одним методом "виклик" або "виконати", а потім інстанціювати їх для виклику методу Вас цікавить (тобто використання анонімного внутрішнього класу).

Ця стаття також може бути цікавою / корисною: Програміст Java дивиться на делегати C # (@ archive.org)


3
Рішення з invoke () як єдиною функцією-членом інтерфейсу дуже приємно.
Стефан Ролланд

1
Але дивіться тут мій приклад того, що можна було б зробити навіть у Java 7, щоб досягти еквівалента делегата C #.
ToolmakerSteve

63

Залежно від того, що саме ви маєте на увазі, ви можете досягти подібного ефекту (обходячи метод) за допомогою стратегії.

Замість такого рядка, де оголошується підпис названого методу:

// C#
public delegate void SomeFunction();

оголосити інтерфейс:

// Java
public interface ISomeBehaviour {
   void SomeFunction();
}

Для конкретних реалізацій методу визначте клас, який реалізує поведінку:

// Java
public class TypeABehaviour implements ISomeBehaviour {
   public void SomeFunction() {
      // TypeA behaviour
   }
}

public class TypeBBehaviour implements ISomeBehaviour {
   public void SomeFunction() {
      // TypeB behaviour
   }
}

Тоді де б ви не мали SomeFunctionделегата в C #, використовуйте ISomeBehaviourзамість цього посилання:

// C#
SomeFunction doSomething = SomeMethod;
doSomething();
doSomething = SomeOtherMethod;
doSomething();

// Java
ISomeBehaviour someBehaviour = new TypeABehaviour();
someBehaviour.SomeFunction();
someBehaviour = new TypeBBehaviour();
someBehaviour.SomeFunction();

За допомогою анонімних внутрішніх класів ви навіть можете уникнути декларування окремих названих класів і майже трактувати їх як реальні функції делегування.

// Java
public void SomeMethod(ISomeBehaviour pSomeBehaviour) {
   ...
}

...

SomeMethod(new ISomeBehaviour() { 
   @Override
   public void SomeFunction() {
      // your implementation
   }
});

Це, мабуть, слід використовувати лише тоді, коли реалізація дуже специфічна для поточного контексту і не буде корисною для повторного використання.

І тоді, звичайно, у Java 8 вони стають в основному лямбда-виразами:

// Java 8
SomeMethod(() -> { /* your implementation */ });

6
+1. це гідний спосіб вирішення, і багатослівність може компенсуватися ремонтом у майбутньому.
nawfal

це чудово ... Наразі працюю над проектом, де я не можу використати рефлексію через обмеження проекту, і цей спосіб вирішує цю роботу прекрасно :)
Джонатан Камарена

Чи має у Java режим I-префіксу іменування інтерфейсів? Я цього раніше не бачив
Кайл Делані

36

Коротка історія: ні .

Вступ

Найновіша версія середовища розробки Microsoft Visual J ++ підтримує мовну конструкцію, яку називають делегатами або зв'язаними посиланнями методів . Ця конструкція, і нові ключові слова delegateта multicastвведено , щоб підтримати його, не є частиною Java TM мови програмування, який вказаний в специфікації мови Java і змінений класами Внутрішніх характеристики , включених в документації по 1.1 програмного забезпечення JDKTM .

Навряд чи мова програмування Java коли-небудь буде включати цю конструкцію. Sun вже ретельно розглядав питання про його прийняття в 1996 році, в міру створення та відмови від працюючих прототипів. Ми зробили висновок, що посилання на пов'язані методи є непотрібними та згубними для мови. Це рішення було прийнято в консультації з Borland International, який мав попередній досвід роботи з пов'язаними методами в Delphi Object Pascal.

Ми вважаємо, що зв'язані посилання на методи не є необхідними, оскільки інша альтернатива дизайну, внутрішні класи , забезпечує рівну або чудову функціональність. Зокрема, внутрішні класи повністю підтримують вимоги поводження з подіями в інтерфейсі користувача та були використані для реалізації API інтерфейсу користувача як мінімум настільки ж всебічного, як і класи фундаменту Windows.

Ми вважаємо, що зв'язані посилання на методи шкідливі, оскільки вони погіршують простоту мови програмування Java та поширений об'єктно-орієнтований характер API. Зв'язані посилання методу також вносять нерегулярність у синтаксис мови та правила визначення рівня. Нарешті, вони розбавляють інвестиції у VM-технології, тому що для віртуальних машин необхідно ефективно обробляти додаткові та розрізнені типи посилань та зв’язок методів.


Як говориться в тому, що Патрік пов'язав, ви хочете використовувати внутрішні класи замість цього.
SCdF

16
Чудова стаття. Я люблю їх визначення "простий": Java призначена для простої мови, як у "Java має бути простою для написання компілятора / VM для", а ігнорування "Java повинна бути простою, щоб писати / читати людиною" . Пояснює багато.
Юозас Контвайніс

5
Я просто думаю, що SUN зробив велику помилку. Їх просто не переконала функціональна парадигма, і це все.
Стефан Ролланд

7
@Juozas: Python чітко зроблений для того, щоб просто писати / читати людиною, і він реалізує лямбда-функції / делегати .
Стефан Ролланд

5
Замінено посиланням archive.org. Крім того, це справді дурно, Oracle.
Патрік

18

Ви читали це :

Делегати - це корисна конструкція в системах на основі подій. По суті Делегати - це об'єкти, які кодують диспетчер методу на визначеному об'єкті. Цей документ показує, як внутрішні класи java забезпечують загальне рішення таких проблем.

Що таке Делегат? Дійсно, він дуже схожий на функцію вказівника на член, яку використовують у C ++. Але делегат містить цільовий об'єкт разом із методом, який слід викликати. В ідеалі було б добре сказати:

obj.registerHandler (ano.methodOne);

..і що метод methodOne буде викликаний ano, коли буде отримано якусь конкретну подію.

Цього досягає структура Делегата.

Внутрішні класи Java

Стверджувалося, що Java надає цю функціональність за допомогою анонімних внутрішніх класів і тому не потребує додаткової конструкції Delegate.

obj.registerHandler(new Handler() {
        public void handleIt(Event ev) {
            methodOne(ev);
        }
      } );

На перший погляд це здається правильним, але в той же час неприємністю. Тому що для багатьох прикладів обробки подій простота синтаксису Delegates є дуже привабливою.

Генеральний обробник

Однак, якщо програмування на основі подій використовується більш розповсюдженим чином, скажімо, наприклад, як частина загального асинхронного середовища програмування, тут більше загрожує.

У такій загальній ситуації недостатньо включати лише цільовий метод та екземпляр цільового об'єкта. Взагалі можуть бути потрібні інші параметри, які визначаються в контексті, коли зареєстрований обробник подій.

У цій більш загальній ситуації підхід java може забезпечити дуже елегантне рішення, особливо в поєднанні з використанням кінцевих змінних:

void processState(final T1 p1, final T2 dispatch) { 
  final int a1 = someCalculation();

  m_obj.registerHandler(new Handler() {
    public void handleIt(Event ev) {
     dispatch.methodOne(a1, ev, p1);
    }
  } );
}

заключний * заключний * фінал

Привернули вашу увагу?

Зауважте, що остаточні змінні є доступними з визначень методу анонімного класу. Не забудьте уважно вивчити цей код, щоб зрозуміти наслідки. Це потенційно дуже потужна техніка. Наприклад, він може бути використаний для хорошого ефекту при реєстрації обробників в MiniDOM та в більш загальних ситуаціях.

Навпаки, конструкція Delegate не пропонує рішення для цієї більш загальної вимоги, і як така повинна бути відхилена як ідіома, на якій можуть базуватися конструкції.


13

Я знаю, що ця публікація стара, але Java 8 додала лямбда і концепцію функціонального інтерфейсу, який представляє собою будь-який інтерфейс лише одним методом. Разом вони пропонують аналогічні функції для делегатів C #. Дивіться тут для отримання додаткової інформації або просто google Java Lambdas. http://cr.openjdk.java.net/~briangoetz/lambda/lambda-state-final.html


5

Ні, але вони підроблені, використовуючи проксі-сервери та роздуми:

  public static class TestClass {
      public String knockKnock() {
          return "who's there?";
      }
  }

  private final TestClass testInstance = new TestClass();

  @Test public void
  can_delegate_a_single_method_interface_to_an_instance() throws Exception {
      Delegator<TestClass, Callable<String>> knockKnockDelegator = Delegator.ofMethod("knockKnock")
                                                                   .of(TestClass.class)
                                                                   .to(Callable.class);
      Callable<String> callable = knockKnockDelegator.delegateTo(testInstance);
      assertThat(callable.call(), is("who's there?"));
  }

Приємна річ у цій ідіомі полягає в тому, що ви можете переконатися, що метод делегованих до цього існує та має необхідну підпис у точці, де ви створюєте делегатор (хоча не під час компіляції, на жаль, хоча плагін FindBugs може бути допомогти тут), а потім безпечно використовувати його для делегування до різних примірників.

Перегляньте код karg на github для отримання додаткових тестів та впровадження .


2

Я реалізував підтримку зворотного дзвінка / делегата в Java за допомогою відображення. Деталі та робоче джерело доступні на моєму веб-сайті .

Як це працює

Існує клас-принцип з іменем Callback з вкладеним класом на ім'я WithParms. API, який потребує зворотного виклику, буде приймати об'єкт зворотного виклику як параметр і, якщо необхідно, створить Callback.WithParms як змінну методу. Оскільки велика кількість застосувань цього об’єкта буде рекурсивною, це працює дуже чисто.

Оскільки ефективність як і раніше залишається для мене високим пріоритетом, я не хотів вимагати, щоб я створював масив об'єктів, що викидає, щоб утримувати параметри для кожного виклику - адже у великій структурі даних може бути тисячі елементів і в обробці повідомлень сценарій ми могли б закінчити обробку тисяч структур даних в секунду.

Для того, щоб бути безпечним у потоці, масив параметрів повинен існувати унікально для кожного виклику методу API, а для ефективності той самий повинен використовуватися для кожного виклику зворотного виклику; Мені знадобився другий об'єкт, який було б дешево створити, щоб зв’язати зворотний виклик з масивом параметрів для виклику. Але, в деяких сценаріях, у виклику вже був би масив параметрів з інших причин. З цих двох причин масив параметрів не належить до об'єкта Callback. Також вибір виклику (передаючи параметри як масив або як окремі об'єкти) належить до рук API, використовуючи зворотний виклик, що дозволяє йому використовувати той виклик, який найкраще підходить для його внутрішнього опрацювання.

Потім вкладений клас WithParms є необов'язковим і виконує дві цілі, він містить масив об'єкта параметрів, необхідний для викликів зворотного виклику, і він пропонує 10 перевантажених методів виклику () (з 1 до 10 параметрів), які завантажують масив параметрів, а потім викликати ціль зворотного дзвінка.

Далі - приклад використання зворотного дзвінка для обробки файлів у дереві каталогів. Це початковий пропуск перевірки, який просто підраховує файли для обробки та гарантує, що жоден не перевищує заздалегідь визначений максимальний розмір. У цьому випадку ми просто створюємо зворотний виклик в рядку з викликом API. Однак ми відображаємо цільовий метод як статичне значення, щоб відображення не робилося кожного разу.

static private final Method             COUNT =Callback.getMethod(Xxx.class,"callback_count",true,File.class,File.class);

...

IoUtil.processDirectory(root,new Callback(this,COUNT),selector);

...

private void callback_count(File dir, File fil) {
    if(fil!=null) {                                                                             // file is null for processing a directory
        fileTotal++;
        if(fil.length()>fileSizeLimit) {
            throw new Abort("Failed","File size exceeds maximum of "+TextUtil.formatNumber(fileSizeLimit)+" bytes: "+fil);
            }
        }
    progress("Counting",dir,fileTotal);
    }

IoUtil.processDirectory ():

/**
 * Process a directory using callbacks.  To interrupt, the callback must throw an (unchecked) exception.
 * Subdirectories are processed only if the selector is null or selects the directories, and are done
 * after the files in any given directory.  When the callback is invoked for a directory, the file
 * argument is null;
 * <p>
 * The callback signature is:
 * <pre>    void callback(File dir, File ent);</pre>
 * <p>
 * @return          The number of files processed.
 */
static public int processDirectory(File dir, Callback cbk, FileSelector sel) {
    return _processDirectory(dir,new Callback.WithParms(cbk,2),sel);
    }

static private int _processDirectory(File dir, Callback.WithParms cbk, FileSelector sel) {
    int                                 cnt=0;

    if(!dir.isDirectory()) {
        if(sel==null || sel.accept(dir)) { cbk.invoke(dir.getParent(),dir); cnt++; }
        }
    else {
        cbk.invoke(dir,(Object[])null);

        File[] lst=(sel==null ? dir.listFiles() : dir.listFiles(sel));
        if(lst!=null) {
            for(int xa=0; xa<lst.length; xa++) {
                File ent=lst[xa];
                if(!ent.isDirectory()) {
                    cbk.invoke(dir,ent);
                    lst[xa]=null;
                    cnt++;
                    }
                }
            for(int xa=0; xa<lst.length; xa++) {
                File ent=lst[xa];
                if(ent!=null) { cnt+=_processDirectory(ent,cbk,sel); }
                }
            }
        }
    return cnt;
    }

Цей приклад ілюструє красу такого підходу - специфічна логіка програми абстрагується в зворотному дзвінку, а зухвалість рекурсивно ходить по дереву каталогів непогано затягується повністю статичним корисним методом багаторазового використання. І нам не доведеться неодноразово платити ціну за визначення та реалізацію інтерфейсу за кожне нове використання. Звичайно, аргумент для інтерфейсу є те , що вона набагато більш чітко про те, що для реалізації (це виконання, а не просто документально) - але на практиці я не знайшов , що це буде проблемою , щоб отримати право визначення зворотного виклику.

Визначення та реалізація інтерфейсу насправді не так вже й погано (якщо ви не розповсюджуєте аплети, як я, коли насправді має значення уникнення створення зайвих класів), але де це насправді світиться, коли у вас є кілька зворотних зворотних дзвінків в одному класі. Мало того, що вони змушені підштовхувати їх кожен до окремого внутрішнього класу, доданого накладні витрати у розгорнуту програму, але це вкрай втомливо програмувати, і весь цей код пластини котла справді просто "шум".


1

Так і ні, але делегатний шаблон на Java можна було б продумати таким чином. У цьому відеоуроці йдеться про обмін даними між активністю - фрагментами, і він має суть делегатного сортового шаблону за допомогою інтерфейсів.

Інтерфейс Java


1

У ньому немає явного delegateключового слова, як C #, але ви можете досягти подібного в Java 8, використовуючи функціональний інтерфейс (тобто будь-який інтерфейс точно одним методом) та лямбда:

private interface SingleFunc {
    void printMe();
}

public static void main(String[] args) {
    SingleFunc sf = () -> {
        System.out.println("Hello, I am a simple single func.");
    };
    SingleFunc sfComplex = () -> {
        System.out.println("Hello, I am a COMPLEX single func.");
    };
    delegate(sf);
    delegate(sfComplex);
}

private static void delegate(SingleFunc f) {
    f.printMe();
}

Кожен новий тип об'єкта SingleFuncповинен реалізовувати printMe(), тому безпечно передати його іншому методу (наприклад delegate(SingleFunc)) для виклику printMe()методу.


0

Хоча він ніде не є таким чистим, але ви можете реалізувати щось на зразок делегатів C # за допомогою Java- проксі .


2
Хоча це посилання може відповісти на питання, краще включити сюди суттєві частини відповіді та надати посилання для довідки. Відповіді лише на посилання можуть стати недійсними, якщо пов’язана сторінка зміниться.
StackFlowed

2
@StackFlowed Стрийте посилання, і що ви отримуєте? Публікація з інформацією. Голосувати за збереження.
Scimonster

1
@Scimonster що буде, якщо посилання більше не дійсна?
StackFlowed

@StackFlowed Він все ще пропонує відповідь - використовуйте проксі Java.
Scimonster

тоді це можна залишити як коментар?
StackFlowed

0

Ні, але вона має подібну поведінку, внутрішньо.

У C # делегати використовуються для створення окремої точки входу, і вони дуже схожі на покажчик функції.

У Java не є вказівник функції (на верхньому погляді), але внутрішньо Java повинна зробити те саме, щоб досягти цих цілей.

Наприклад, для створення потоків у Java потрібен клас, що розширює Thread або реалізує Runnable, тому що змінна об'єкта класу може використовуватися вказівником розташування пам'яті.



0

Описаний код пропонує багато переваг делегатів C #. Методи, статичні чи динамічні, можна обробляти рівномірно. Складність виклику методів через рефлексію зменшується, і код є багаторазовим, тобто не потребує додаткових класів у коді користувача. Зверніть увагу: ми викликаємо альтернативну зручну версію виклику, де метод з одним параметром можна викликати, не створюючи масив об'єктів. Код Java нижче:

  class Class1 {
        public void show(String s) { System.out.println(s); }
    }

    class Class2 {
        public void display(String s) { System.out.println(s); }
    }

    // allows static method as well
    class Class3 {
        public static void staticDisplay(String s) { System.out.println(s); }
    }

    public class TestDelegate  {
        public static final Class[] OUTPUT_ARGS = { String.class };
        public final Delegator DO_SHOW = new Delegator(OUTPUT_ARGS,Void.TYPE);

        public void main(String[] args)  {
            Delegate[] items = new Delegate[3];

            items[0] = DO_SHOW .build(new Class1(),"show,);
            items[1] = DO_SHOW.build (new Class2(),"display");
            items[2] = DO_SHOW.build(Class3.class, "staticDisplay");

            for(int i = 0; i < items.length; i++) {
                items[i].invoke("Hello World");
            }
        }
    }

-11

У Java немає делегатів і пишається цим :). З того, що я прочитав тут, я виявив, по суті, 2 способи підробки делегатів: 1. рефлексія; 2. внутрішній клас

Роздуми slooooow! Внутрішній клас не охоплює найпростішого використання: функцію сортування. Не хочу вникати в деталі, але рішення з внутрішнім класом в основному полягає у створенні класу обгортки для масиву цілих чисел, відсортованих у порядку зростання та класу для масиву цілих чисел, відсортованих у порядку зменшення.


Який внутрішній клас пов'язаний із сортуванням? А яке сортування пов'язане з питанням?
nawfal
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.