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 8 및 Java 언어 사양 §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에서 진행중인 작업에 대해 듣고 싶습니다.
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();
}
}
그래서 우리가 여기있는 것은 무엇입니까?
- 기능적 인터페이스-이것은 하나의 메소드 선언 만 포함하는 @FunctionalInterface로 어노테이션이 있거나없는 인터페이스 입니다.
- 메소드 참조-이것은 단지 특별한 구문이며, objectInstance :: methodName 과 유사합니다 .
- 사용 예-할당 연산자와 인터페이스 메소드 호출.
귀하는 청취자에게만 기능 인터페이스를 사용해야합니다.
다른 모든 함수 포인터는 코드 가독성과 이해 능력이 실제로 좋지 않기 때문입니다. 그러나 직접적인 메소드 참조는 예를 들어 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);
'development' 카테고리의 다른 글
div가 표시 될 때 작업을 트리거하는 jQuery 이벤트 (0) | 2020.03.15 |
---|---|
javadoc에서 메소드 매개 변수에 대한 참조를 추가하는 방법은 무엇입니까? (0) | 2020.03.15 |
GitHub가 SSH를 통한 HTTPS를 권장하는 이유는 무엇입니까? (0) | 2020.03.14 |
“.NET Core”란 무엇입니까? (0) | 2020.03.14 |
OutputStream을 InputStream로 변환하는 방법? (0) | 2020.03.14 |