development

Java에서 함수 포인터를 대체하는 가장 가까운 것은 무엇입니까?

big-blog 2020. 3. 15. 09:36
반응형

Java에서 함수 포인터를 대체하는 가장 가까운 것은 무엇입니까?


약 10 줄의 코드가있는 방법이 있습니다. 한 줄의 코드를 변경하는 작은 계산을 제외하고는 정확히 동일한 작업을 수행하는 더 많은 메서드를 만들고 싶습니다. 이것은 한 줄을 대체하기 위해 함수 포인터를 전달하는 데 완벽한 응용 프로그램이지만 Java에는 함수 포인터가 없습니다. 최선의 대안은 무엇입니까?


익명의 내부 클래스

String를 반환하는 매개 변수 와 함께 함수를 전달하려고한다고 가정 해보십시오 int.
먼저 기존 인터페이스를 재사용 할 수없는 경우 함수를 사용하는 인터페이스를 유일한 멤버로 정의해야합니다.

interface StringFunction {
    int func(String param);
}

포인터를 취하는 메소드는 다음 StringFunction과 같이 인스턴스를 받아 들입니다.

public void takingMethod(StringFunction sf) {
   int i = sf.func("my string");
   // do whatever ...
}

그리고 이렇게 불려질 것입니다 :

ref.takingMethod(new StringFunction() {
    public int func(String param) {
        // body
    }
});

편집 : Java 8에서는 람다 식으로 호출 할 수 있습니다.

ref.takingMethod(param -> bodyExpression);

각 "함수 포인터"에 대해 계산을 구현 하는 작은 functor 클래스만듭니다 . 모든 클래스가 구현할 인터페이스를 정의하고 해당 객체의 인스턴스를 더 큰 함수로 전달하십시오. 이것은 " 명령 패턴 "과 " 전략 패턴 " 의 조합입니다 .

@ sblundy의 예가 좋습니다.


한 줄에서 미리 정의 된 수의 서로 다른 계산이있을 경우 열거 형을 사용하면 전략 패턴을 빠르고 명확하게 구현할 수 있습니다.

public enum Operation {
    PLUS {
        public double calc(double a, double b) {
            return a + b;
        }
    },
    TIMES {
        public double calc(double a, double b) {
            return a * b;
        }
    }
     ...

     public abstract double calc(double a, double b);
}

분명히, 각 구현의 정확히 하나의 인스턴스뿐만 아니라 전략 방법 선언도 모두 단일 클래스 / 파일에 정의됩니다.


전달하려는 함수를 제공하는 인터페이스를 작성해야합니다. 예 :

/**
 * A simple interface to wrap up a function of one argument.
 * 
 * @author rcreswick
 *
 */
public interface Function1<S, T> {

   /**
    * Evaluates this function on it's arguments.
    * 
    * @param a The first argument.
    * @return The result.
    */
   public S eval(T a);

}

그런 다음 함수를 전달해야 할 때 해당 인터페이스를 구현할 수 있습니다.

List<Integer> result = CollectionUtilities.map(list,
        new Function1<Integer, Integer>() {
           @Override
           public Integer eval(Integer a) {
              return a * a;
           }
        });

마지막으로 맵 함수는 다음과 같이 Function1에 전달 된 것을 사용합니다.

   public static <K,R,S,T> Map<K, R> zipWith(Function2<R,S,T> fn, 
         Map<K, S> m1, Map<K, T> m2, Map<K, R> results){
      Set<K> keySet = new HashSet<K>();
      keySet.addAll(m1.keySet());
      keySet.addAll(m2.keySet());

      results.clear();

      for (K key : keySet) {
         results.put(key, fn.eval(m1.get(key), m2.get(key)));
      }
      return results;
   }

매개 변수를 전달할 필요가없는 경우 종종 자체 인터페이스 대신 Runnable을 사용할 수 있거나 다양한 다른 기술을 사용하여 매개 변수 수를 덜 "고정"하게 만들 수 있지만 일반적으로 유형 안전과의 상충 관계입니다. 또는 함수 객체의 생성자를 재정 의하여 매개 변수를 그런 식으로 전달할 수 있습니다. 많은 방법이 있으며 일부 상황에서는 더 잘 작동합니다.


::연산자를 사용한 메소드 참조

메소드가 기능 인터페이스를 허용하는 메소드 인수에서 메소드 참조를 사용할 수 있습니다 . 기능적 인터페이스는 하나의 추상 메소드 만 포함하는 모든 인터페이스입니다. 기능 인터페이스에는 하나 이상의 기본 메소드 또는 정적 메소드가 포함될 수 있습니다.

IntBinaryOperator기능적인 인터페이스입니다. 그것의 추상 메소드 는 매개 변수로 applyAsInt두 개의을 허용 int하고를 반환합니다 int. Math.max또한 두 개의을 허용하고을 int반환합니다 int. 이 예에서, A.method(Math::max);parameter.applyAsInt에 두 개의 입력 값을 전송 Math.max하고 그 결과를 리턴 Math.max.

import java.util.function.IntBinaryOperator;

class A {
    static void method(IntBinaryOperator parameter) {
        int i = parameter.applyAsInt(7315, 89163);
        System.out.println(i);
    }
}
import java.lang.Math;

class B {
    public static void main(String[] args) {
        A.method(Math::max);
    }
}

일반적으로 다음을 사용할 수 있습니다.

method1(Class1::method2);

대신에:

method1((arg1, arg2) -> Class1.method2(arg1, arg2));

이것은 짧다 :

method1(new Interface1() {
    int method1(int arg1, int arg2) {
        return Class1.method2(arg1, agr2);
    }
});

자세한 내용 은 Java 8Java 언어 사양 §15.13의 :: (이중 콜론) 연산자를 참조하십시오 .


이 작업을 수행 할 수도 있습니다 (일부 RARE 경우에 의미가 있음). 문제는 큰 문제입니다. 클래스 / 인터페이스 사용의 모든 유형 안전성을 잃어 버리고 메서드가 존재하지 않는 경우를 처리해야합니다.

액세스 제한을 무시하고 개인 메소드를 호출 할 수있는 "혜택"이 있습니다 (예에서는 표시되지 않지만 컴파일러가 일반적으로 호출 할 수없는 메소드를 호출 할 수 있음).

다시 말하지만, 이것이 의미가있는 경우는 드물지만 그러한 경우에는 좋은 도구입니다.

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

class Main
{
    public static void main(final String[] argv)
        throws NoSuchMethodException,
               IllegalAccessException,
               IllegalArgumentException,
               InvocationTargetException
    {
        final String methodName;
        final Method method;
        final Main   main;

        main = new Main();

        if(argv.length == 0)
        {
            methodName = "foo";
        }
        else
        {
            methodName = "bar";
        }

        method = Main.class.getDeclaredMethod(methodName, int.class);

        main.car(method, 42);
    }

    private void foo(final int x)
    {
        System.out.println("foo: " + x);
    }

    private void bar(final int x)
    {
        System.out.println("bar: " + x);
    }

    private void car(final Method method,
                     final int    val)
        throws IllegalAccessException,
               IllegalArgumentException,
               InvocationTargetException
    {
        method.invoke(this, val);
    }
}

한 줄만 다른 경우 플래그와 같은 매개 변수를 추가하고 한 줄을 호출하는 if (flag) 문을 추가 할 수 있습니다.


클로저와 관련하여 Java 7에서 진행중인 작업에 대해 듣고 싶습니다.

Java에서 현재 클로저 상태는 무엇입니까?

http://gafter.blogspot.com/2006/08/closures-for-java.html
http://tech.puredanger.com/java7/#closures


연산자를 사용한 새로운 Java 8 기능 인터페이스메소드 참조::

Java 8은 " @ Functional Interface "포인터로 메소드 참조 (MyClass :: new)를 유지할 수 있습니다. 동일한 메소드 이름이 필요하지 않으며 동일한 메소드 서명 만 필요합니다.

예:

@FunctionalInterface
interface CallbackHandler{
    public void onClick();
}

public class MyClass{
    public void doClick1(){System.out.println("doClick1");;}
    public void doClick2(){System.out.println("doClick2");}
    public CallbackHandler mClickListener = this::doClick;

    public static void main(String[] args) {
        MyClass myObjectInstance = new MyClass();
        CallbackHandler pointer = myObjectInstance::doClick1;
        Runnable pointer2 = myObjectInstance::doClick2;
        pointer.onClick();
        pointer2.run();
    }
}

그래서 우리가 여기있는 것은 무엇입니까?

  1. 기능적 인터페이스-이것은 하나의 메소드 선언 만 포함하는 @FunctionalInterface로 어노테이션이 있거나없는 인터페이스 입니다.
  2. 메소드 참조-이것은 단지 특별한 구문이며, objectInstance :: methodName 과 유사합니다 .
  3. 사용 예-할당 연산자와 인터페이스 메소드 호출.

귀하는 청취자에게만 기능 인터페이스를 사용해야합니다.

다른 모든 함수 포인터는 코드 가독성과 이해 능력이 실제로 좋지 않기 때문입니다. 그러나 직접적인 메소드 참조는 예를 들어 foreach와 같이 편리합니다.

몇 가지 사전 정의 된 기능 인터페이스가 있습니다.

Runnable              -> void run( );
Supplier<T>           -> T get( );
Consumer<T>           -> void accept(T);
Predicate<T>          -> boolean test(T);
UnaryOperator<T>      -> T apply(T);
BinaryOperator<T,U,R> -> R apply(T, U);
Function<T,R>         -> R apply(T);
BiFunction<T,U,R>     -> R apply(T, U);
//... and some more of it ...
Callable<V>           -> V call() throws Exception;
Readable              -> int read(CharBuffer) throws IOException;
AutoCloseable         -> void close() throws Exception;
Iterable<T>           -> Iterator<T> iterator();
Comparable<T>         -> int compareTo(T);
Comparator<T>         -> int compare(T,T);

이전 Java 버전의 경우 Adrian Petrescu가 위에서 언급 한 것과 유사한 기능 및 구문을 가진 Guava Libraries를 사용해야합니다.

추가 연구는 Java 8 Cheatsheet를 참조하십시오.

Java Language Specification §15.13 링크에 대한 The Guy with The Hat 덕분에 감사합니다 .


@ sblundy의 대답은 훌륭하지만 익명의 내부 클래스에는 두 가지 작은 결함이 있습니다 .1 차는 재사용 할 수없는 경향이 있고 2 차는 큰 구문입니다.

좋은 점은 그의 패턴이 메인 클래스 (계산을 수행하는 클래스)를 변경하지 않고 풀 클래스로 확장된다는 것입니다.

새 클래스를 인스턴스화 할 때 방정식에서 상수로 작동 할 수있는 해당 클래스에 매개 변수를 전달할 수 있습니다. 따라서 내부 클래스 중 하나가 다음과 같은 경우

f(x,y)=x*y

그러나 때로는 다음 중 하나가 필요합니다.

f(x,y)=x*y*2

그리고 아마도 세 번째입니다 :

f(x,y)=x*y/2

두 개의 익명 내부 클래스를 만들거나 "통과"매개 변수를 추가하는 대신 다음과 같이 인스턴스화하는 단일 ACTUAL 클래스를 만들 수 있습니다.

InnerFunc f=new InnerFunc(1.0);// for the first
calculateUsing(f);
f=new InnerFunc(2.0);// for the second
calculateUsing(f);
f=new InnerFunc(0.5);// for the third
calculateUsing(f);

단순히 상수를 클래스에 저장하고 인터페이스에 지정된 메소드에서 사용합니다.

실제로 함수가 저장 / 재사용되지 않는다는 것을 알고 있다면 다음과 같이 할 수 있습니다.

InnerFunc f=new InnerFunc(1.0);// for the first
calculateUsing(f);
f.setConstant(2.0);
calculateUsing(f);
f.setConstant(0.5);
calculateUsing(f);

그러나 불변의 클래스는 더 안전합니다.이 클래스를 변경 가능하게 만드는 타당성을 제시 할 수는 없습니다.

나는 익명의 내부 클래스가 들릴 때마다 울기 때문에 실제로 이것을 게시합니다. 프로그래머가 실제로 클래스를 사용해야했을 때 익명이 되었기 때문에 "필수"인 많은 중복 코드를 보았습니다. 그의 결정을 다시 생각했다.


인기가 높아지고있는 Google Guava 라이브러리 에는 API의 많은 부분에서 작업 한 일반 함수술어 객체가 있습니다.


나에게 전략 패턴처럼 들린다. fluffycat.com Java 패턴을 확인하십시오.


Java로 프로그래밍 할 때 정말로 놓친 것 중 하나는 함수 콜백입니다. 이것들이 계속해서 제시되어야하는 상황 중 하나는 각 항목에 대해 특정 작업을 수행하려는 계층을 재귀 적으로 처리하는 것이 었습니다. 디렉토리 트리를 걷거나 데이터 구조를 처리하는 것과 같습니다. 내 미니멀리스트는 인터페이스를 정의한 다음 각 특정 사례에 대한 구현을 정의해야합니다.

어느 날 나는 왜 안 궁금해하는 것을 발견했다? 메소드 포인터-Method 객체가 있습니다. JIT 컴파일러를 최적화하면 반사 호출은 더 이상 큰 성능 저하를 초래하지 않습니다. 또한 파일을 한 위치에서 다른 위치로 복사하는 것 외에도 반영된 메소드 호출 비용은 무의미합니다.

그것에 대해 더 많이 생각했을 때 OOP 패러다임의 콜백은 객체와 메소드를 바인딩해야합니다. 콜백 객체를 입력하십시오.

Java의 콜백에 대한 리플렉션 기반 솔루션을 확인하십시오 . 어떤 용도로든 무료


이 스레드는 이미 오래되었으므로 아마도 내 대답이 질문에 도움이되지 않을 것입니다. 그러나이 스레드가 내 솔루션을 찾는 데 도움이되었으므로 어쨌든 여기에 배치 할 것입니다.

알려진 입력과 알려진 출력 ( double )이 있는 가변 정적 메소드를 사용해야했습니다 . 그런 다음 메소드 패키지와 이름을 알고 다음과 같이 작업 할 수 있습니다.

java.lang.reflect.Method Function = Class.forName(String classPath).getMethod(String method, Class[] params);

하나의 double을 매개 변수로 받아들이는 함수의 경우.

그래서 구체적인 상황에서 나는 그것을 초기화했습니다.

java.lang.reflect.Method Function = Class.forName("be.qan.NN.ActivationFunctions").getMethod("sigmoid", double.class);

나중에 더 복잡한 상황에서 호출했습니다.

return (java.lang.Double)this.Function.invoke(null, args);

java.lang.Object[] args = new java.lang.Object[] {activity};
someOtherFunction() + 234 + (java.lang.Double)Function.invoke(null, args);

여기서 활동은 임의의 이중 값입니다. SoftwareMonkey가 한 것처럼이 작업을 좀 더 추상화하고 일반화하려고 생각하고 있지만 현재는 그 방식에 만족합니다. 클래스와 인터페이스가 필요하지 않은 세 줄의 코드는 그리 나쁘지 않습니다.


함수 배열에 대한 인터페이스없이 동일한 작업을 수행하려면

class NameFuncPair
{
    public String name;                // name each func
    void   f(String x) {}              // stub gets overridden
    public NameFuncPair(String myName) { this.name = myName; }
}

public class ArrayOfFunctions
{
    public static void main(String[] args)
    {
        final A a = new A();
        final B b = new B();

        NameFuncPair[] fArray = new NameFuncPair[]
        {
            new NameFuncPair("A") { @Override void f(String x) { a.g(x); } },
            new NameFuncPair("B") { @Override void f(String x) { b.h(x); } },
        };

        // Go through the whole func list and run the func named "B"
        for (NameFuncPair fInstance : fArray)
        {
            if (fInstance.name.equals("B"))
            {
                fInstance.f(fInstance.name + "(some args)");
            }
        }
    }
}

class A { void g(String args) { System.out.println(args); } }
class B { void h(String args) { System.out.println(args); } }

lambdaj를 확인하십시오

http://code.google.com/p/lambdaj/

특히 새로운 폐쇄 기능

http://code.google.com/p/lambdaj/wiki/Closures

의미없는 인터페이스를 만들거나 추악한 내부 클래스를 사용하지 않고 클로저 또는 함수 포인터를 정의하는 매우 읽기 쉬운 방법을 찾을 수 있습니다.


와우, 왜 이미 java에 대해 수행 한 것을 감안할 때 Delegate 클래스를 작성하여 T가 반환 유형이 아닌 매개 변수를 전달하는 데 사용하십시오. 죄송하지만 일반적으로 Java를 배우는 C ++ / C # 프로그래머는 매우 편리하기 때문에 함수 포인터가 필요합니다. 분석법 정보를 다루는 수업에 익숙하다면 할 수 있습니다. java.lang.reflect.method가 될 Java 라이브러리에서.

항상 인터페이스를 사용하는 경우 항상 인터페이스를 구현해야합니다. 이벤트 처리에서는 처리기 목록에서 등록 / 등록 취소하는 것이 더 좋은 방법은 아니지만 값 유형이 아닌 함수를 전달 해야하는 델리게이트의 경우 인터페이스를 클래스 외부에서 처리하기 위해 델리게이트 클래스를 만듭니다.


Java 8 답변 중 어느 것도 완전한 응집력있는 예를 제공하지 않았으므로 여기에옵니다.

다음과 같이 "함수 포인터"를 허용하는 메소드를 선언하십시오.

void doCalculation(Function<Integer, String> calculation, int parameter) {
    final String result = calculation.apply(parameter);
}

함수에 람다 식을 제공하여 호출하십시오.

doCalculation((i) -> i.toString(), 2);

누구든지 하나의 매개 변수 집합을 사용하여 동작을 정의하지만 Scheme과 같은 다른 매개 변수 집합을 실행하는 함수를 전달하는 데 어려움을 겪고있는 경우 :

(define (function scalar1 scalar2)
  (lambda (x) (* x scalar1 scalar2)))

Java에서 매개 변수 정의 동작으로 함수 전달을 참조하십시오.


Java8부터 공식 SE 8 API에 라이브러리가있는 람다를 사용할 수 있습니다.

사용법 : 하나의 추상 메소드 만있는 인터페이스를 사용해야합니다. 다음과 같이 인스턴스를 만듭니다 (이미 제공된 Java SE 8을 사용하고 싶을 수도 있음).

Function<InputType, OutputType> functionname = (inputvariablename) {
... 
return outputinstance;
}

자세한 정보는 다음 문서를 확인하십시오. https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html


Java 8 이전에는 함수 포인터와 유사한 기능을 가장 가까운 대체물이 익명 클래스였습니다. 예를 들면 다음과 같습니다.

Collections.sort(list, new Comparator<CustomClass>(){
    public int compare(CustomClass a, CustomClass b)
    {
        // Logic to compare objects of class CustomClass which returns int as per contract.
    }
});

그러나 이제 Java 8에서는 lambda expression 이라는 매우 깔끔한 대안이 있으며 다음과 같이 사용할 수 있습니다.

list.sort((a, b) ->  { a.isBiggerThan(b) } );

여기서 isBiggerThan은의 메소드입니다 CustomClass. 여기에서 메소드 참조를 사용할 수도 있습니다.

list.sort(MyClass::isBiggerThan);

참고 URL : https://stackoverflow.com/questions/122407/whats-the-nearest-substitute-for-a-function-pointer-in-java

반응형