자바 대표?


194

Java 언어에는 C #이 대리자를 지원하는 방식과 비슷한 대리자 기능이 있습니까?


20
@Suma 언급 한 질문이 1 년 후에 게시 된 경우 어떻게 중복 될 수 있습니까?
애니

1
Java 8에는 대리자와 매우 유사한 기능이 있습니다. 이것을 람다라고합니다.
tbodt

4
@tbodt가 더 정확하면 Java 8에는 대리자와 매우 유사한 기능이 있습니다. 기능 인터페이스라고합니다 . 람다는 이러한 대리자 인스턴스를 익명으로 만드는 한 가지 방법입니다.
nawfal

3
아래 답변은 Pre-Java 8에서 온 것입니다. Java 8
이후이

@nawfal, +1이지만 더 정확하려면 Java 7 이하에는 이미 델리게이트와 같은 기능이 있습니다. 이를 일반 인터페이스라고합니다.
Pacerier

답변:


152

아니, 아니

리플렉션을 사용하여 호출 할 수있는 Methods 객체를 가져와 동일한 효과를 얻을 수 있으며, 다른 방법은 단일 'invoke'또는 'execute'메소드로 인터페이스를 만든 다음 인스턴스화하여 메소드를 호출하는 것입니다. 익명의 내부 클래스 사용에 관심이 있습니다.

이 기사가 흥미롭고 유용하다는 것을 알 수있을 것이다 : Java 프로그래머가 C # Delegates (@ archive.org)


3
인터페이스의 유일한 멤버 함수로 invoke ()를 사용하는 솔루션이 정말 좋습니다.
Stephane Rolland

1
하지만 내 예를 참조 여기에 C #을 위임에 동등을 달성하기 위해, 심지어 자바 7에서, 하나는 어떻게 할 것인지의.
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
   }
}

그런 다음 SomeFunctionC #에서 대리자 가 있었을 때마다 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- 접두사 명명 규칙이 있습니까? 나는 전에 그것을 보지 못했다
Kyle Delaney

36

짧은 이야기 : 아니다 .

소개

Microsoft Visual J ++ 개발 환경의 최신 버전은 대리자 또는 바인딩 된 메서드 참조 라는 언어 구성을 지원합니다 . 이 구조, 그리고 새로운 키워드 delegatemulticast이를 지원하기 위해 도입은 자바의 일부가 아닌 TM에 의해 지정되는 프로그래밍 언어, Java 언어 사양 에 의해 및 개정 사양 내부 클래스 에 포함 JDKTM 1.1 소프트웨어에 대한 문서 .

Java 프로그래밍 언어가이 구성을 포함하지는 않을 것입니다. Sun은 이미 프로토 타입 제작 및 폐기에 이르기까지 1996 년에 채택을 신중하게 고려했습니다. 우리의 결론은 바인딩 된 메소드 참조가 불필요하고 언어에 해롭다는 것입니다. 이 결정은 Delphi Object Pascal에서 바인딩 된 메소드 참조에 대한 경험이있는 Borland International과상의하여 이루어졌습니다.

우리는 바인딩 된 메소드 참조가 불필요하다고 생각합니다. 다른 디자인 대안 인 inner class 는 동일하거나 우수한 기능을 제공 하기 때문 입니다. 특히 내부 클래스는 사용자 인터페이스 이벤트 처리 요구 사항을 완벽하게 지원하며 최소한 Windows Foundation 클래스만큼 포괄적 인 사용자 인터페이스 API를 구현하는 데 사용되었습니다.

우리는 바인딩 된 메소드 참조가 Java 프로그래밍 언어의 단순성과 API의 객체 지향적 특성을 떨어 뜨리기 때문에 유해 하다고 생각 합니다. 바운드 메소드 참조는 언어 구문 및 범위 지정 규칙에 불규칙성을 유발합니다. 마지막으로 VM은 별도의 다른 유형의 참조 및 메서드 연결을 효율적으로 처리해야하므로 VM 기술에 대한 투자가 줄어 들었습니다.


Patrick이 링크 한 내용에서 말하는 것처럼 내부 클래스를 대신 사용하려고합니다.
SCdF

16
좋은 기사. "단순"에 대한 그들의 정의를 좋아합니다. Java는 "자바가 사람이 쉽게 읽고 쓸 수 있어야한다"를 무시하면서 "자바는 컴파일러 / VM을 작성하는 것이 간단해야합니다"와 같이 간단한 언어로 설계되었습니다. . 많이 설명합니다.
Juozas Kontvainis

5
나는 단지 SUN이 큰 실수를했다고 생각합니다. 그들은 기능적 패러다임에 의해 확신을 얻지 못했으며 그게 전부입니다.
Stephane Rolland

7
@Juozas : Python은 사람이 쓰기 / 읽기 간단하게 람다 함수 / delegates를 구현 합니다.
Stephane Rolland

5
archive.org 링크로 대체되었습니다. 또한, 그것은 정말 바보입니다, 오라클.
Patrick

18

읽은 :

대리인은 이벤트 기반 시스템에서 유용한 구성입니다. 본질적으로 위임은 지정된 객체에서 메소드 디스패치를 ​​인코딩하는 객체입니다. 이 문서는 Java 내부 클래스가 어떻게 이러한 문제에 대한보다 일반적인 솔루션을 제공하는지 보여줍니다.

대리인이란 무엇입니까? 실제로 C ++에서 사용되는 멤버 함수에 대한 포인터와 매우 유사합니다. 그러나 델리게이트에는 호출 할 메소드와 함께 대상 오브젝트가 포함됩니다. 이상적으로 말할 수 있으면 좋을 것입니다.

obj.registerHandler (ano.methodOne);

.. 그리고 특정 이벤트가 수신되면 메소드 methodOne이 ano에서 호출됩니다.

이것이 델리게이트 구조가 달성하는 것입니다.

자바 내부 클래스

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 플러그인은 컴파일 타임에 없지만) 여기에서 도움을 받으십시오) 그런 다음 안전하게 사용하여 다양한 인스턴스에 위임하십시오.

더 많은 테스트구현 에 대해서는 githubkarg 코드를 참조하십시오 .


2

리플렉션을 사용하여 Java에서 콜백 / 델리게이트 지원을 구현했습니다. 자세한 내용과 작업 소스는 내 웹 사이트에서 확인할 수 있습니다 .

작동 원리

WithParms라는 중첩 클래스가있는 Callback이라는 기본 클래스가 있습니다. 콜백이 필요한 API는 콜백 객체를 매개 변수로 사용하고 필요한 경우 메소드 변수로 Callback.WithParms를 만듭니다. 이 객체의 많은 응용 프로그램이 재귀 적이므로 매우 깨끗하게 작동합니다.

성능이 여전히 나에게 우선 순위가 높기 때문에 모든 호출에 대한 매개 변수를 보유하는 객관적인 객체 배열을 만들고 싶지 않았습니다. 결국 큰 데이터 구조에는 수천 개의 요소가 있으며 메시지 처리가 가능합니다. 시나리오 우리는 초당 수천 개의 데이터 구조를 처리 할 수 ​​있습니다.

스레드 안전을 위해서는 매개 변수 배열이 API 메소드를 호출 할 때마다 고유하게 존재해야하며 효율성을 위해 콜백을 호출 할 때마다 동일한 배열을 사용해야합니다. 콜백을 호출하기 위해 매개 변수 배열로 바인딩하기 위해 생성하는 저렴한 두 번째 객체가 필요했습니다. 그러나 일부 시나리오에서는 호출자가 다른 이유로 이미 매개 변수 배열을 가지고있을 것입니다. 이 두 가지 이유로 인해 매개 변수 배열은 콜백 객체에 속하지 않습니다. 또한 매개 변수를 배열 또는 개별 객체로 전달하는 호출 선택은 콜백을 사용하여 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의 델리게이트 패턴은 이런 식으로 생각할 수 있습니다. 이 비디오 자습서 는 활동-프래그먼트 간 데이터 교환에 관한 것이며 인터페이스를 사용하여 델리게이트 정렬 패턴의 본질을 가지고 있습니다.

자바 인터페이스


1

delegateC #과 같은 명시적인 키워드는 없지만 기능적 인터페이스 (예 : 정확히 하나의 메소드가있는 인터페이스)와 람다를 사용하여 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

거의 깨끗한 곳은 아니지만 Java 프록시를 사용하여 C # 대리자와 같은 것을 구현할 수 있습니다.


2
이 링크가 질문에 대답 할 수 있지만 여기에 답변의 필수 부분을 포함시키고 참조 할 수있는 링크를 제공하는 것이 좋습니다. 링크 된 페이지가 변경되면 링크 전용 답변이 유효하지 않을 수 있습니다.
StackFlowed

2
@StackFlowed 링크를 제거하면 무엇을 얻을 수 있습니까? 정보가 포함 된 게시물입니다. 계속 투표하십시오.
Scimonster

1
@Scimonster 링크가 더 이상 유효하지 않으면 어떻게됩니까?
StackFlowed 2009 년

@StackFlowed 여전히 답변을 제공합니다. Java 프록시를 사용하십시오.
Scimonster

그러면 코멘트로 남을 수 있습니까?
StackFlowed 2009 년

0

아니요, 그러나 내부적으로 비슷한 동작을합니다.

C #에서는 대리자가 별도의 진입 점을 만드는 데 사용되며 함수 포인터와 매우 유사하게 작동합니다.

Java에서는 함수 포인터 (상단에)가 없지만 내부적으로 Java는 이러한 목표를 달성하기 위해 동일한 작업을 수행해야합니다.

예를 들어 Java에서 스레드를 만들려면 클래스 객체 변수를 메모리 위치 포인터로 사용할 수 있으므로 Thread를 확장하거나 Runnable을 구현하는 클래스가 필요합니다.



0

설명 된 코드는 C # 델리게이트의 많은 장점을 제공합니다. 정적 또는 동적 방법은 균일 한 방식으로 처리 될 수 있습니다. 리플렉션을 통한 메소드 호출의 복잡성이 줄어들고 사용자 코드에 추가 클래스가 필요 없다는 의미에서 코드를 재사용 할 수 있습니다. 객체 배열을 만들지 않고 하나의 매개 변수를 가진 메서드를 호출 할 수있는 대체 편의 버전의 호출을 호출하고 있습니다.

  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. 내부 클래스

반사는 slooooow입니다! 내부 클래스는 가장 간단한 유스 케이스 (정렬 함수)를 다루지 않습니다. 세부 사항으로 들어가고 싶지는 않지만 내부 클래스의 해결책은 기본적으로 정수 배열을 오름차순으로 정렬하고 정수 배열을 내림차순으로 정렬하는 래퍼 클래스를 만드는 것입니다.


내부 클래스는 정렬과 어떤 관련이 있습니까? 그리고 질문과는 어떤 관계가 있습니까?
nawfal
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.