Делегаты Java?


194

Имеет ли язык Java функции делегатов, подобно тому, как в C # есть поддержка делегатов?


20
@Suma Как это может быть дубликатом, если упомянутый вами вопрос был опубликован через год после этого?
Ани

1
Java 8 имеет особенность, похожую на делегаты. Это называется лямбда.
tbodt

4
Если быть более точным, @tbodt, в Java 8 есть такая функция, как делегаты. Это называется функциональными интерфейсами . Лямбды являются одним из способов создания таких экземпляров делегатов (анонимно).
Nawfal

3
Ответы ниже взяты из Pre-Java 8. Опубликовать Java 8 см. Ответы в этой теме: stackoverflow.com/questions/20311779/…
Майкл Ллойд Ли mlk

@nawfal, +1, но, если быть более точным, Java 7 и ниже уже имеет функцию, похожую на делегаты. Это называется простые интерфейсы.
Pacerier

Ответы:


152

Не на самом деле нет.

Вы можете достичь того же эффекта, используя отражение, чтобы получить объекты метода, которые вы затем можете вызывать, а другой способ - создать интерфейс с единственным методом 'invoke' или 'execute', а затем создать их экземпляр для вызова метода. ваш интерес (т.е. использование анонимного внутреннего класса).

Вы также можете найти эту статью интересной / полезной: 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 и дополнен спецификацией внутренних классов, включенной в документацию для программного обеспечения JDKTM 1.1 .

Маловероятно, что язык программирования Java будет когда-либо включать эту конструкцию. Sun уже тщательно продумала вопрос о его принятии в 1996 году, чтобы создавать и отбрасывать рабочие прототипы. Мы пришли к выводу, что ссылки на связанные методы не нужны и вредны для языка. Это решение было принято в консультации с Borland International, у которого был предыдущий опыт работы со ссылками на связанные методы в Delphi Object Pascal.

Мы полагаем, что ссылки на связанные методы не нужны, потому что другая альтернатива дизайна, внутренние классы , обеспечивает равную или превосходящую функциональность. В частности, внутренние классы полностью поддерживают требования обработки событий пользовательского интерфейса и используются для реализации API пользовательского интерфейса, по крайней мере, такого же всеобъемлющего, как классы Windows Foundation.

Мы считаем, что ссылки на связанные методы вредны, потому что они умаляют простоту языка программирования Java и широко распространенный объектно-ориентированный характер API. Ссылки на связанные методы также вносят нерегулярность в синтаксис языка и правила области видимости. Наконец, они уменьшают инвестиции в технологии виртуальных машин, потому что виртуальные машины необходимы для эффективной обработки дополнительных и разнородных типов ссылок и методов связи.


Как говорится в том, что связал Патрик, вы хотите вместо этого использовать внутренние классы.
SCdF

16
Отличная статья. Я ЛЮБЛЮ их определение «простой»: Java разработан, чтобы быть простым языком, как в «Java должен быть простым для написания компилятора / ВМ для«, в то время как игнорирование «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);
        }
      } );

На первый взгляд это кажется правильным, но в то же время неудобством. Потому что для многих примеров обработки событий простота синтаксиса делегатов очень привлекательна.

Генеральный хендлер

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

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

В этой более общей ситуации 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 и в более общих ситуациях.

Напротив, конструкция «Делегат» не предоставляет решения для этого более общего требования, и как таковая должна быть отклонена как идиома, на которой могут основываться конструкции.


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 в качестве параметра и, если необходимо, создаст Callback.WithParms в качестве переменной метода. Поскольку многие из приложений этого объекта будут рекурсивными, это работает очень чисто.

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

Для обеспечения безопасности потоков массив параметров должен существовать уникально для каждого вызова метода API, и для эффективности его следует использовать для каждого вызова обратного вызова; Мне нужен был второй объект, который было бы дешево создать, чтобы связать обратный вызов с массивом параметров для вызова. Но в некоторых сценариях вызывающий уже имеет массив параметров по другим причинам. По этим двум причинам массив параметров не принадлежит объекту Callback. Кроме того, выбор вызова (передача параметров в виде массива или отдельных объектов) принадлежит API, использующему обратный вызов, позволяющий использовать любой вызов, который лучше всего подходит для его внутренней работы.

Тогда вложенный класс WithParms является необязательным и служит двум целям: он содержит массив объектов параметров, необходимый для вызовов обратного вызова, и предоставляет 10 перегруженных методов invoke () (с числом параметров от 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 Proxy .


2
Хотя эта ссылка может ответить на вопрос, лучше включить сюда основные части ответа и предоставить ссылку для справки. Ответы, содержащие только ссылки, могут стать недействительными, если связанная страница изменится.
StackFlowed

2
@StackFlowed Уберите ссылку, и что вы получите? Пост с информацией. Голосуй, чтобы сохранить.
Scimonster

1
@ Scimonster, что произойдет, если ссылка больше не действительна?
StackFlowed

@StackFlowed Это все еще дает ответ - используйте Прокси Java.
Scimonster

тогда это можно оставить как комментарий?
StackFlowed

0

Нет, но у него похожее поведение внутри.

В C # делегаты используются для создания отдельной точки входа, и они работают так же, как указатель на функцию.

В Java нет такого понятия, как указатель на функцию (на верхнем уровне), но внутренне Java должна делать то же самое для достижения этих целей.

Например, для создания потоков в Java требуется расширение класса Thread или реализация Runnable, поскольку переменная объекта класса может быть использована в указателе расположения памяти.



0

Описанный код предлагает множество преимуществ делегатов C #. Методы, статические или динамические, могут обрабатываться единообразно. Сложность в вызове методов с помощью отражения снижается, и код можно использовать повторно, в том смысле, что не требуется никаких дополнительных классов в пользовательском коде. Обратите внимание, что мы вызываем альтернативную удобную версию invoke, где метод с одним параметром может быть вызван без создания массива объектов. Код 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. внутренний класс

Размышления неопрятные! Внутренний класс не охватывает самый простой вариант использования: функция сортировки. Не хочу вдаваться в подробности, но решение с внутренним классом в основном состоит в том, чтобы создать класс-оболочку для массива целых чисел, которые будут отсортированы в порядке возрастания, и класс для массива целых чисел, которые будут отсортированы в порядке убывания.


Какое отношение имеет внутренний класс к сортировке? И при чем тут сортировка?
nawfal
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.