Java Delegeleri?


194

Java dili, C #'ın temsilciler için desteğine benzer şekilde temsilci özelliklere sahip mi?


20
@Suma Bahsettiğiniz soru bundan bir yıl sonra gönderildiyse, bu nasıl tekrarlanabilir?
Ani

1
Java 8, delegelere çok benzer bir özelliğe sahiptir. Buna lambda denir.
Haziran'da tbodt

4
@tbodt daha doğru olmak gerekirse, Java 8 delegelere çok benzer bir özelliğe sahiptir. Buna fonksiyonel arayüzler denir . Lambdas, bu tür delege örnekleri oluşturmanın bir yoludur (anonim olarak).
nawfal

3
Aşağıdaki cevaplar Pre-Java 8'den alınmıştır. Java 8 sonrası bu konudaki cevaplara bakın: stackoverflow.com/questions/20311779/…
Michael Lloyd Lee mlk

@nawfal, +1, ancak daha da doğrusu, Java 7 ve altı zaten delegeler gibi bir özelliğe sahiptir. Buna düz arayüzler denir.
Pacerier

Yanıtlar:


152

Gerçekten değil, hayır.

Daha sonra çağırabileceğiniz Yöntem nesnelerini almak için yansıma kullanarak aynı efekti elde edebilirsiniz ve diğer yol, tek bir 'invoke' veya 'execute' yöntemiyle bir arabirim oluşturmak ve ardından yöntemi çağırmak için bunları başlatmaktır. (yani isimsiz bir iç sınıf kullanmak).

Bu makaleyi ilginç / yararlı da bulabilirsiniz: Bir Java Programcısı C # Delegates'e Bakıyor (@ archive.org)


3
Bir arabirimin tek üye işlevi olarak invoke () ile çözüm gerçekten güzel.
Stephane Rolland

1
Ama bir C # delege eşdeğerini gerçekleştirmek için Java 7 bile, ne yapacağımı burada benim örnek bakın .
ToolmakerSteve

63

Tam olarak ne demek istediğinize bağlı olarak, Strateji Kalıbı'nı kullanarak benzer bir etki (bir yöntemden geçerek) elde edebilirsiniz.

Adlandırılmış bir yöntem imzası bildiren böyle bir satır yerine:

// C#
public delegate void SomeFunction();

bir arayüz bildirin:

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

Yöntemin somut uygulamaları için, davranışı uygulayan bir sınıf tanımlayın:

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

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

Sonra SomeFunctionC # 'da bir temsilci olsaydı, ISomeBehaviourbunun yerine bir başvuru kullanın:

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

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

İsimsiz iç sınıflarla, ayrı adlandırılmış sınıflar bildirmekten bile kaçınabilir ve neredeyse onlara gerçek delege işlevleri gibi davranabilirsiniz.

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

...

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

Bu muhtemelen sadece uygulama mevcut bağlama çok özel olduğunda ve yeniden kullanılmadan fayda sağlamayacaksa kullanılmalıdır.

Ve elbette Java 8'de bunlar temel olarak lambda ifadeleri haline gelir:

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

6
+1. bu iyi bir çözümdür ve ayrıntı düzeyi gelecekteki sürdürülebilirlik ile telafi edilebilir.
nawfal

bu harika ... Şu anda proje kısıtlamaları nedeniyle yansımayı kullanamadığım bir proje üzerinde çalışıyor ve bu geçici çözüm işi güzel bir şekilde yapıyor :)
Jonathan Camarena

Java'nın arabirimler için bir I-ön ek adlandırma kuralı var mı? Bunu daha önce görmedim
Kyle Delaney

36

Kısa öykü: hayır .

Giriş

Microsoft Visual J ++ geliştirme ortamının en yeni sürümü, delegeler veya ilişkili yöntem başvuruları adlı bir dil yapısını destekler . Bu yapı, ve yeni anahtar delegateve multicastbunu destekleyecek tanıtıldı Java bir parçası değildir TM tarafından belirtilen programlama dili, Java Dil Şartnamede tarafından ve tadil Şartname İç Sınıflar dahil JDKTM 1.1 yazılımı belgelerine .

Java programlama dilinin bu yapıyı içermesi olası değildir. Sun, çalışma prototiplerini oluşturma ve atma ölçüsünde, 1996 yılında bunu benimsemeyi dikkatlice değerlendirdi. Sonuç olarak, bağlı yöntem referanslarının gereksiz ve dile zarar verici olduğu sonucuna varılmıştır. Bu karar daha önce Delphi Object Pascal'da ilişkili yöntem referansları konusunda deneyim sahibi olan Borland International'a danışarak alındı.

Sınırlı yöntem referanslarının gereksiz olduğuna inanıyoruz çünkü başka bir tasarım alternatifi, iç sınıflar eşit veya üstün işlevsellik sağlar. Özellikle, iç sınıflar kullanıcı arabirimi olay işleme gereksinimlerini tam olarak destekler ve en azından Windows Foundation Sınıfları kadar kapsamlı bir kullanıcı arabirimi API'sini uygulamak için kullanılmıştır.

Sınırlı yöntem referanslarının zararlı olduğuna inanıyoruz, çünkü Java programlama dilinin basitliğinden ve API'ların yaygın nesne yönelimli karakterinden uzaklaşmaktadırlar. Bağlı yöntem referansları ayrıca dil sözdizimi ve kapsam belirleme kurallarına düzensizlik getirir. Son olarak, VM teknolojilerine yapılan yatırımı sulandırırlar çünkü VM'lerin ek ve farklı tipte referansları ve yöntem bağlantısını verimli bir şekilde ele almaları gerekir.


Patrick'in bağlantı verdiği şeyde söylediği gibi, bunun yerine iç sınıfları kullanmak istiyorsunuz.
SCdF

16
Harika makale. Ben onların "basit" tanımlarını SEVİYORUM: Java basit bir dil olarak tasarlanmıştır. . Çok açıklar.
Juozas Kontvainis

5
Sadece SUN'un büyük bir hata yaptığını düşünüyorum. İşlevsel paradigma tarafından ikna olmamışlar, hepsi bu.
Stephane Rolland

7
@Juozas: Python, bir kişi tarafından yazılması / okunması basit olarak açıklanmıştır ve lambda işlevlerini / delegelerini uygular .
Stephane Rolland

5
Bir archive.org bağlantısıyla değiştirildi. Ayrıca, bu gerçekten aptalca Oracle.
Patrick

18

Okumak mı bu :

Delegeler olay tabanlı sistemlerde yararlı bir yapıdır. Esasen Temsilciler, belirtilen bir nesne üzerinde yöntem gönderimini kodlayan nesnelerdir. Bu belge, java iç sınıflarının bu tür sorunlara nasıl daha genel bir çözüm sunduğunu göstermektedir.

Delege nedir? Gerçekten C ++ kullanılan bir işaretçi üye işlevine çok benzer. Ancak bir temsilci, çağrılacak yöntemle birlikte hedef nesneyi içerir. İdeal olarak şunu söyleyebiliriz:

obj.registerHandler (ano.methodOne);

..ve yöntemOne yöntemi, belirli bir olay alındığında ano'da çağrılır.

Delege yapısının başarısı budur.

Java İç Sınıfları

Java'nın bu işlevselliği anonim iç sınıflar aracılığıyla sağladığı ve dolayısıyla ek Delege yapısına ihtiyaç duymadığı ileri sürülmüştür.

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

İlk bakışta bu doğru görünüyor ama aynı zamanda bir sıkıntı. Çünkü birçok olay işleme örneği için Delegeler sözdiziminin sadeliği çok caziptir.

Genel İşleyici

Ancak, olaya dayalı programlama daha yaygın bir şekilde kullanılırsa, örneğin genel bir asenkron programlama ortamının bir parçası olarak, söz konusu olandan daha fazlası söz konusudur.

Böyle bir genel durumda, sadece hedef yöntemi ve hedef nesne örneğini dahil etmek yeterli değildir. Genel olarak, olay işleyicisi kaydedildiğinde bağlam içinde belirlenen başka parametreler de olabilir.

Bu daha genel durumda, java yaklaşımı, özellikle son değişkenlerin kullanımı ile birleştirildiğinde çok zarif bir çözüm sağlayabilir:

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);
    }
  } );
}

final * final * final

Dikkatin var mı?

Nihai değişkenlere anonim sınıf yöntemi tanımlarından erişilebilir. Sonuçları anlamak için bu kodu dikkatle incelediğinizden emin olun. Bu potansiyel olarak çok güçlü bir tekniktir. Örneğin, işleyicileri MiniDOM'a kaydederken ve daha genel durumlarda iyi bir etki için kullanılabilir.

Aksine, Delege yapısı bu daha genel gereksinim için bir çözüm sağlamaz ve bu nedenle tasarımların dayandığı bir deyim olarak reddedilmelidir.



5

Hayır, ancak proxy'ler ve yansıma kullanarak taklit edilebilirler:

  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?"));
  }

Bu deyim ile ilgili güzel bir şey, temsilci oluşturma yönteminin var olduğunu ve gerekli imzayı bulunduğunu doğrulayabilmenizdir. burada yardım), ardından çeşitli örneklere yetki vermek için güvenle kullanın.

Daha fazla test ve uygulama için github'daki karg koduna bakın .


2

Yansıma kullanarak Java'da geri arama / temsilci desteği uyguladım. Ayrıntılar ve çalışma kaynağı web sitemde mevcuttur .

Nasıl çalışır

WithParms adında bir iç içe sınıf ile Geri Arama adlı bir ilke sınıfı vardır. Geri aramaya ihtiyaç duyan API, parametre olarak bir Callback nesnesini alır ve gerekirse bir yöntem değişkeni olarak bir Callback.WithParms oluşturur. Bu nesnenin birçok uygulaması özyinelemeli olacağından, bu çok temiz çalışır.

Performans hala benim için yüksek bir öncelik ile, her çağırma için parametreleri tutmak için bir ıskarta nesne dizisi oluşturmak için gerekli değildi - sonuçta büyük bir veri yapısında binlerce öğe ve bir mesaj işleme olabilir senaryo saniyede binlerce veri yapısını işleyebiliriz.

Thread güvenli olması için, parametre dizisinin API yönteminin her çağrılması için benzersiz olarak mevcut olması gerekir ve verimlilik için, geri çağrının her çağrılması için aynı dizinin kullanılması gerekir; Ben geri çağırma çağırma için bir parametre dizisi ile bağlamak için oluşturmak için ucuz olurdu ikinci bir nesne gerekiyordu. Ancak, bazı senaryolarda, invoker zaten başka nedenlerle bir parametre dizisine sahip olacaktır. Bu iki nedenden ötürü, parametre dizisi Geri Arama nesnesine ait değildir. Ayrıca, invokasyon seçimi (parametreleri bir dizi veya tek tek nesneler olarak geçirme), iç çalışmalarına en uygun invokasyonun hangisinin uygun olduğunu kullanmasını sağlayan geri arama kullanılarak API'nın elindedir.

WithParms nested class, o zaman isteğe bağlıdır ve iki amaca hizmet eder, geri arama çağrıları için gereken parametre nesnesi dizisini içerir ve parametre dizisini yükleyen ve daha sonra 10 aşırı yüklenmiş invoke () yöntemi (1'den 10 parametreye kadar) sağlar ve geri arama hedefini çağırır.

Aşağıda dizin dizinindeki dosyaları işlemek için bir geri çağrı kullanan bir örnek verilmiştir. Bu, yalnızca işlenecek dosyaları sayan ve hiçbirinin önceden belirlenmiş bir maksimum boyutu aşmadığından emin olan bir ilk doğrulama geçişidir. Bu durumda API geri çağırma ile satır içi geri arama oluştururuz. Ancak, hedef yöntemi statik bir değer olarak yansıtıyoruz, böylece yansıma her seferinde yapılmıyor.

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;
    }

Bu örnek, bu yaklaşımın güzelliğini göstermektedir - uygulamaya özgü mantık geri aramaya soyutlanmıştır ve bir dizin ağacını tekrar tekrar yürüdüğünün hilesi tamamen yeniden kullanılabilir bir statik yardımcı yöntemde güzelce sıkışmıştır. Ve her yeni kullanım için bir arayüz tanımlamanın ve uygulamanın fiyatını tekrar tekrar ödemek zorunda değiliz. Tabii ki, bir arabirimin argümanı , neyin uygulanacağı konusunda çok daha açık olmasıdır (sadece belgelendirilmiş değil, zorlanmıştır) - ancak pratikte geri arama tanımını doğru yapmanın bir sorun olduğunu bulamadım.

Bir arabirimi tanımlamak ve uygulamak gerçekten o kadar da kötü değildir (benim gibi, ekstra sınıflar oluşturmaktan kaçınmanın nerede olduğu önemli değildir), ancak tek bir sınıfta birden fazla geri çağırma yaptığınız zaman bunun gerçekten parladığı yerdir. Bunların her birini konuşlandırılan uygulamada ek bir iç sınıfa eklenen ek yüke zorlamakla kalmaz, aynı zamanda programlamak tamamen zahmetlidir ve tüm bu kazan plakası kodu gerçekten sadece "gürültü" dür.


1

Evet ve Hayır, ancak Java'daki delege kalıbı bu şekilde düşünülebilir. Bu eğitim videosu , aktivite parçaları arasındaki veri alışverişiyle ilgilidir ve arayüzler kullanarak temsilci sorta modelinin büyük özüne sahiptir.

Java Arayüzü


1

delegateC # olarak açık bir anahtar kelimesi yoktur, ancak Java 8'de fonksiyonel bir arayüz (yani tam olarak tek bir yöntemle herhangi bir arayüz) ve lambda kullanarak benzer bir anahtar kelime elde edebilirsiniz:

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();
}

Her tür yeni nesne SingleFuncuygulanmalıdır printMe(), bu nedenle yöntemi delegate(SingleFunc)çağırmak için başka bir yönteme (ör. ) Geçirmek güvenlidir printMe().


0

Neredeyse hiçbir yerde temiz olmasa da, Java Proxy kullanarak C # delegeleri gibi bir şey uygulayabilirsiniz .


2
Bu bağlantı soruyu cevaplayabilse de, cevabın temel kısımlarını buraya eklemek ve bağlantıyı referans olarak sağlamak daha iyidir. Bağlantı verilen sayfa değişirse, yalnızca bağlantı yanıtları geçersiz olabilir.
StackFlowed

2
@StackFlowed Bağlantıyı kaldırın ve ne elde edersiniz? Bilgi içeren bir yazı. Saklamak için oy verin.
Scimonster

1
@Scimonster bağlantı artık geçerli değilse ne olur?
StackFlowed

@StackFlowed Hala bir yanıt sağlar - Java Proxy kullanın.
Scimonster

yorum olarak bırakılabilir mi?
StackFlowed

0

Hayır, ancak içsel olarak benzer bir davranışa sahiptir.

C # delegeleri ayrı bir giriş noktası oluşturmak için kullanılır ve bir işlev işaretçisi gibi çalışır.

Java'da işlev işaretçisi (üst görünümde) diye bir şey yoktur, ancak Java'nın bu hedeflere ulaşmak için aynı şeyi yapması gerekir.

Örneğin, Java'da bir iş parçacığı oluşturmak, bir iş parçacığı genişletme veya Runnable'ı uygulama için bir sınıf gerektirir, çünkü bir sınıf nesnesi değişkeni bir bellek konum işaretçisi kullanılabilir.



0

Açıklanan kod, C # delegelerinin birçok avantajını sunar. Statik veya dinamik yöntemler tek tip bir şekilde tedavi edilebilir. Yansıtma yoluyla yöntemlerin çağrılmasındaki karmaşıklık azalır ve kod, kullanıcı kodunda ek sınıf gerektirmeyecek şekilde yeniden kullanılabilir. Unutmayın, invoke'un alternatif bir kolaylık sürümünü çağırıyoruz, burada bir parametreli bir yöntem bir nesne dizisi oluşturmadan çağrılabilir.

  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 delegesi yok ve gurur duyuyor :). Burada okuduğum kadarıyla, sahte delegeleri iki şekilde buldum: 1. yansıma; 2. iç sınıf

Yansımalar slooooow! İç sınıf, en basit use-case: sort işlevini kapsamaz. Ayrıntılara girmek istemiyorum, ancak iç sınıfla ilgili çözüm temel olarak bir dizi tam sayı dizisinin artan düzende sıralanması ve bir dizi tam sayı dizisinin azalan düzende sıralanmasıdır.


İç sınıfın sıralama ile ne ilgisi var? Ve soru ile ne tür bir sıralama var?
nawfal
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.