Чи має мова Java функції делегата, подібно до того, як C # має підтримку делегатів?
Чи має мова Java функції делегата, подібно до того, як C # має підтримку делегатів?
Відповіді:
Не дуже, ні.
Можливо, ви зможете досягти того ж ефекту, використовуючи відображення, щоб отримати об'єкти Method, до яких ви можете потім викликати, а інший спосіб - створити інтерфейс з одним методом "виклик" або "виконати", а потім інстанціювати їх для виклику методу Вас цікавить (тобто використання анонімного внутрішнього класу).
Ця стаття також може бути цікавою / корисною: Програміст Java дивиться на делегати C # (@ archive.org)
Залежно від того, що саме ви маєте на увазі, ви можете досягти подібного ефекту (обходячи метод) за допомогою стратегії.
Замість такого рядка, де оголошується підпис названого методу:
// 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 */ });
Вступ
Найновіша версія середовища розробки Microsoft Visual J ++ підтримує мовну конструкцію, яку називають делегатами або зв'язаними посиланнями методів . Ця конструкція, і нові ключові слова
delegate
таmulticast
введено , щоб підтримати його, не є частиною Java TM мови програмування, який вказаний в специфікації мови Java і змінений класами Внутрішніх характеристики , включених в документації по 1.1 програмного забезпечення JDKTM .Навряд чи мова програмування Java коли-небудь буде включати цю конструкцію. Sun вже ретельно розглядав питання про його прийняття в 1996 році, в міру створення та відмови від працюючих прототипів. Ми зробили висновок, що посилання на пов'язані методи є непотрібними та згубними для мови. Це рішення було прийнято в консультації з Borland International, який мав попередній досвід роботи з пов'язаними методами в Delphi Object Pascal.
Ми вважаємо, що зв'язані посилання на методи не є необхідними, оскільки інша альтернатива дизайну, внутрішні класи , забезпечує рівну або чудову функціональність. Зокрема, внутрішні класи повністю підтримують вимоги поводження з подіями в інтерфейсі користувача та були використані для реалізації API інтерфейсу користувача як мінімум настільки ж всебічного, як і класи фундаменту Windows.
Ми вважаємо, що зв'язані посилання на методи шкідливі, оскільки вони погіршують простоту мови програмування Java та поширений об'єктно-орієнтований характер API. Зв'язані посилання методу також вносять нерегулярність у синтаксис мови та правила визначення рівня. Нарешті, вони розбавляють інвестиції у VM-технології, тому що для віртуальних машин необхідно ефективно обробляти додаткові та розрізнені типи посилань та зв’язок методів.
Ви читали це :
Делегати - це корисна конструкція в системах на основі подій. По суті Делегати - це об'єкти, які кодують диспетчер методу на визначеному об'єкті. Цей документ показує, як внутрішні класи 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 не пропонує рішення для цієї більш загальної вимоги, і як така повинна бути відхилена як ідіома, на якій можуть базуватися конструкції.
Я знаю, що ця публікація стара, але Java 8 додала лямбда і концепцію функціонального інтерфейсу, який представляє собою будь-який інтерфейс лише одним методом. Разом вони пропонують аналогічні функції для делегатів C #. Дивіться тут для отримання додаткової інформації або просто google Java Lambdas. http://cr.openjdk.java.net/~briangoetz/lambda/lambda-state-final.html
Ні, але вони підроблені, використовуючи проксі-сервери та роздуми:
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 для отримання додаткових тестів та впровадження .
Я реалізував підтримку зворотного дзвінка / делегата в 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;
}
Цей приклад ілюструє красу такого підходу - специфічна логіка програми абстрагується в зворотному дзвінку, а зухвалість рекурсивно ходить по дереву каталогів непогано затягується повністю статичним корисним методом багаторазового використання. І нам не доведеться неодноразово платити ціну за визначення та реалізацію інтерфейсу за кожне нове використання. Звичайно, аргумент для інтерфейсу є те , що вона набагато більш чітко про те, що для реалізації (це виконання, а не просто документально) - але на практиці я не знайшов , що це буде проблемою , щоб отримати право визначення зворотного виклику.
Визначення та реалізація інтерфейсу насправді не так вже й погано (якщо ви не розповсюджуєте аплети, як я, коли насправді має значення уникнення створення зайвих класів), але де це насправді світиться, коли у вас є кілька зворотних зворотних дзвінків в одному класі. Мало того, що вони змушені підштовхувати їх кожен до окремого внутрішнього класу, доданого накладні витрати у розгорнуту програму, але це вкрай втомливо програмувати, і весь цей код пластини котла справді просто "шум".
Так і ні, але делегатний шаблон на Java можна було б продумати таким чином. У цьому відеоуроці йдеться про обмін даними між активністю - фрагментами, і він має суть делегатного сортового шаблону за допомогою інтерфейсів.
У ньому немає явного 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()
методу.
Хоча він ніде не є таким чистим, але ви можете реалізувати щось на зразок делегатів C # за допомогою Java- проксі .
Ні, але вона має подібну поведінку, внутрішньо.
У C # делегати використовуються для створення окремої точки входу, і вони дуже схожі на покажчик функції.
У Java не є вказівник функції (на верхньому погляді), але внутрішньо Java повинна зробити те саме, щоб досягти цих цілей.
Наприклад, для створення потоків у Java потрібен клас, що розширює Thread або реалізує Runnable, тому що змінна об'єкта класу може використовуватися вказівником розташування пам'яті.
Ні, у Java немає такої дивовижної функції. Але ви можете створити його вручну, використовуючи шаблон спостерігача. Ось приклад: Напишіть делегат C # в java
Описаний код пропонує багато переваг делегатів 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");
}
}
}
У Java немає делегатів і пишається цим :). З того, що я прочитав тут, я виявив, по суті, 2 способи підробки делегатів: 1. рефлексія; 2. внутрішній клас
Роздуми slooooow! Внутрішній клас не охоплює найпростішого використання: функцію сортування. Не хочу вникати в деталі, але рішення з внутрішнім класом в основному полягає у створенні класу обгортки для масиву цілих чисел, відсортованих у порядку зростання та класу для масиву цілих чисел, відсортованих у порядку зменшення.