Java에서 기능 포인터의 가장 가까운 대용품은 무엇입니까?
-
02-07-2019 - |
문제
약 10 줄의 코드 인 메소드가 있습니다. 코드 한 줄을 변경하는 작은 계산을 제외하고는 정확히 같은 작업을 수행하는 더 많은 방법을 만들고 싶습니다. 이것은 해당 한 줄을 대체하기 위해 함수 포인터를 전달하기위한 완벽한 응용 프로그램이지만 Java에는 기능 포인터가 없습니다. 내 최고의 대안은 무엇입니까?
해결책
익명의 내부 수업
기능을 전달하고 싶다고 말합니다. String
반환하는 매개 변수 int
.
먼저 기존의 재사용을 재사용 할 수없는 경우 함수의 유일한 멤버로서 인터페이스를 정의해야합니다.
interface StringFunction {
int func(String param);
}
포인터를 취하는 방법은 그냥 받아 들일 것입니다 StringFunction
SO와 같은 인스턴스 :
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에서는 Lambda 표현으로 호출 할 수 있습니다.
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;
}
매개 변수를 전달할 필요가없는 경우 자신의 인터페이스 대신 런 가능을 사용하거나 다양한 기타 기술을 사용하여 Param Count를 "고정"할 수 있지만 일반적으로 유형 안전과 상충됩니다. (또는 당신은 당신의 기능 개체의 생성자를 그런 식으로 전달할 수 있도록 만들 수 있습니다. 많은 접근 방식이 있으며 일부 상황에서는 더 잘 작동합니다.)
메소드 참조를 사용합니다 ::
운영자
메소드 인수에서 메소드 참조를 사용할 수 있습니다. 메소드가 기능 인터페이스. 기능 인터페이스는 하나의 추상 방법 만 포함하는 인터페이스입니다. (기능적 인터페이스에는 하나 이상의 기본 메소드 또는 정적 방법이 포함될 수 있습니다.)
IntBinaryOperator
기능 인터페이스입니다. 추상적 인 방법, applyAsInt
, 두 가지를 받아들입니다 int
s 매개 변수로서 반환합니다 int
. Math.max
또한 두 가지를 받아들입니다 int
s와 반환 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.
당신은 또한 이것을 할 수 있습니다 (어떤 경우에는 희귀한 경우가 의미가 있습니다). 문제는 클래스/인터페이스를 사용하는 모든 유형 안전성을 잃고 메소드가 존재하지 않는 경우를 처리해야한다는 것입니다.
액세스 제한을 무시하고 개인 메소드를 호출 할 수있는 "혜택"이 있습니다 (예제에 표시되지 않지만 컴파일러가 일반적으로 호출하지 못하는 메소드를 호출 할 수 있습니다).
다시 말하지만, 이것이 의미가있는 것은 드문 경우이지만, 그 경우에는 좋은 도구입니다.
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은 메소드 참조 (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 라이브러리와 구문을 사용해야합니다.
추가 연구를 보려면 Java 8 치트 시트
그리고 모자를 쓴 사람에게 감사합니다 Java 언어 사양 §15.13 링크.
@sblundy의 대답은 훌륭하지만 익명의 내부 클래스는 두 가지 작은 결함을 가지고 있으며, 기본은 재사용 할 수없는 경향이 있고 2 차는 부피가 큰 구문입니다.
좋은 점은 그의 패턴이 메인 클래스의 변화없이 전체 클래스로 확장된다는 것입니다 (계산을 수행하는 사람).
새 클래스를 인스턴스화하면 매개 변수를 해당 클래스로 전달할 수 있으며,이 클래스 중 하나가 다음과 같이 보이는 경우 다음과 같습니다.
f(x,y)=x*y
그러나 때로는 다음과 같은 것이 필요합니다.
f(x,y)=x*y*2
그리고 아마도 세 번째는 다음과 같습니다.
f(x,y)=x*y/2
두 개의 익명의 내부 클래스를 만들거나 "통과"매개 변수를 추가하는 대신 다음과 같이 인스턴스화하는 단일 실제 클래스를 만들 수 있습니다.
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);
그러나 불변의 수업은 더 안전합니다. 저는 이와 같은 수업을 만들기 위해 정당화를 내놓을 수 없습니다.
익명의 내부 수업을들을 때마다 울기 때문에 나는 이것을 실제로 게시합니다. 프로그래머가 처음으로 한 첫 번째 일은 실제 클래스를 사용했을 때 익명이었고 결코 필요했기 때문에 "필요한"중복 코드를 많이 보았습니다. 그의 결정을 다시 생각했습니다.
나에게 전략 패턴처럼 들린다. fluffycat.com Java 패턴을 확인하십시오.
Java에서 프로그래밍 할 때 내가 실제로 놓친 것 중 하나는 기능 콜백입니다. 이들 자체가 계속 제시 해야하는 한 가지 상황은 각 항목에 대해 특정 조치를 수행하려는 계층 구조를 재귀 적으로 처리하는 것이 었습니다. 디렉토리 트리를 걷거나 데이터 구조를 처리하는 것과 같습니다. 내부의 미니멀리스트는 인터페이스를 정의한 다음 각 특정 사례에 대한 구현을 정의해야합니다.
어느 날 나는 왜 그렇지 않은지 궁금해하는 것을 발견했습니다. 메소드 포인터 - 메소드 객체가 있습니다. JIT 컴파일러를 최적화하면 반사 호출은 더 이상 큰 성능 페널티를받지 않습니다. 예를 들어, 한 위치에서 다른 위치로 파일을 복사하는 것 외에도 반사 된 메소드 호출의 비용은 무의미합니다.
더 많이 생각했듯이 OOP 패러다임의 콜백은 객체와 메소드를 함께 바인딩해야한다는 것을 깨달았습니다. 콜백 객체를 입력하십시오.
반사 기반 솔루션을 확인하십시오 Java의 콜백. 모든 용도로 무료.
좋아,이 스레드는 이미 충분히 늙었으므로 아마도 내 대답은 질문에 도움이되지 않습니다. 그러나이 스레드는 내 솔루션을 찾는 데 도움이되었으므로 어쨌든 여기에 넣을 것입니다.
알려진 입력 및 알려진 출력이있는 가변 정적 메소드를 사용해야했습니다 (둘 다 더블). 따라서 메소드 패키지와 이름을 알면 다음과 같이 작업 할 수 있습니다.
java.lang.reflect.Method Function = Class.forName(String classPath).getMethod(String method, Class[] params);
하나를 매개 변수로 받아들이는 함수의 경우.
그래서 내 구체적인 상황에서 나는 그것을 초기화했다
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를 위해했던 것을 감안할 때 그다지 어려운 대의원 클래스를 만들고 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);
}
Lambda 표현을 제공하여 기능을 부르십시오.
doCalculation((i) -> i.toString(), 2);
누군가가 동작을 정의하기 위해 하나의 매개 변수 세트를 사용하는 함수를 전달하는 데 어려움을 겪고있는 경우, Scheme과 같이 실행할 다른 매개 변수 세트.
(define (function scalar1 scalar2)
(lambda (x) (* x scalar1 scalar2)))
Java8이므로 공식 SE 8 API에 라이브러리가있는 Lambdas를 사용할 수 있습니다.
용법:하나의 추상 방법 만 사용하면 인터페이스를 사용해야합니다. 다음과 같이 인스턴스 (이미 제공된 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에서는 다음과 같은 매우 깔끔한 대안이 있습니다. 람다 표현, 다음과 같이 사용할 수 있습니다.
list.sort((a, b) -> { a.isBiggerThan(b) } );
isbiggerthan은 방법입니다 CustomClass
. 여기에서 메소드 참조를 사용할 수도 있습니다.
list.sort(MyClass::isBiggerThan);