[java] 자바 대표?

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



답변

아니, 아니

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

이 기사가 흥미롭고 유용하다는 것을 알 수있을 것이다 : Java 프로그래머가 C # Delegates (@ 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
   }
}

그런 다음 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 */ });


답변

짧은 이야기 : 아니다 .

소개

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

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

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

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


답변

읽은 :

대리인은 이벤트 기반 시스템에서 유용한 구성입니다. 본질적으로 위임은 지정된 객체에서 메소드 디스패치를 ​​인코딩하는 객체입니다. 이 문서는 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 구문은 이러한 일반적인 요구 사항에 대한 솔루션을 제공하지 않으므로 디자인을 기반으로 할 수있는 관용구로 거부해야합니다.


답변

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

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


답변

리플렉션을 사용하여 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;
    }

이 예제는 이러한 접근 방식의 아름다움을 보여줍니다. 응용 프로그램 별 논리는 콜백으로 추상화되며 디렉토리 트리를 재귀 적으로 걸어가는 번거 로움은 완전히 재사용 가능한 정적 유틸리티 방법으로 잘 정리되어 있습니다. 또한 모든 새로운 용도에 대해 인터페이스를 정의하고 구현하는 비용을 반복해서 지불 할 필요가 없습니다. 물론 인터페이스 의 주장 구현해야 할 것에 대해 훨씬 더 명확하다는 것입니다 (단순히 문서화 된 것이 아니라 시행 됨). 그러나 실제로 콜백 정의를 올바르게 얻는 데 문제가되지는 않았습니다.

인터페이스를 정의하고 구현하는 것은 실제로 나쁘지 않습니다 (추가 클래스를 만드는 것을 피하는 것이 중요한 애플릿을 배포하지 않는 한), 단일 클래스에서 여러 개의 콜백이있을 때 실제로 빛나는 곳입니다. 배포 된 응용 프로그램에서 별도의 내부 클래스 추가 오버 헤드로 각각을 밀어야 할뿐만 아니라 프로그래밍하는 것은 지루하고 보일러 플레이트 코드는 실제로 “노이즈”입니다.