Что является ближайшей заменой указателю на функцию в Java?
У меня есть метод, который составляет около десяти строк кода. Я хочу создать больше методов, которые будут делать то же самое, за исключением небольшого вычисления, которое изменит одну строку кода. Это идеальное приложение для передачи указателя на функцию, чтобы заменить эту строку, но в Java нет указателей на функции. Какая моя лучшая альтернатива?
21 ответ
Анонимный внутренний класс
Скажем, вы хотите, чтобы функция передавалась с 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);
Для каждого "указателя на функцию" я бы создал небольшой класс функторов, который реализует ваши вычисления. Определите интерфейс, который будут реализовывать все классы, и передайте экземпляры этих объектов в вашу большую функцию. Это комбинация " шаблон команды" и " шаблон стратегии".
Пример @sblundy хорош.
Если в одной строке задано заранее определенное количество различных вычислений, использование enum - быстрый, но понятный способ реализации шаблона стратегии.
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;
}
});
Наконец, функция map использует переданную функцию 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
s в качестве параметров и возвращает 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.
Вы также можете сделать это (что в некоторых редких случаях имеет смысл). Проблема (и это большая проблема) заключается в том, что вы теряете всю безопасность типов при использовании класса / интерфейса, и вам приходится иметь дело со случаем, когда метод не существует.
У него есть "преимущество", заключающееся в том, что вы можете игнорировать ограничения доступа и вызывать закрытые методы (не показано в примере, но вы можете вызывать методы, которые компилятор обычно не позволяет вам вызывать).
Опять же, это редкий случай, когда это имеет смысл, но в таких случаях это хороший инструмент.
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/
Новые функциональные интерфейсы Java 8 и ссылки на методы, использующие ::
оператор.
Java 8 может поддерживать ссылки на методы ( MyClass::new) с помощью указателей " @ Functional Interface ". Нет необходимости использовать одно и то же имя метода, требуется только одна и та же сигнатура метода.
Пример:
@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 вы должны попробовать библиотеки Guava, которые имеют схожую функциональность и синтаксис, как уже упоминал Адриан Петреску.
Для дополнительных исследований посмотрите на Java 8 Cheatsheet
и спасибо Парню со Шляпой для ссылки Спецификации языка Java §15.13.
Ответ @sblundy великолепен, но у анонимных внутренних классов есть два небольших недостатка, основной из которых состоит в том, что они, как правило, не подлежат повторному использованию, а вторичный - громоздкий синтаксис.
Приятно то, что его шаблон расширяется до полных классов без каких-либо изменений в основном классе (который выполняет вычисления).
Когда вы создаете экземпляр нового класса, вы можете передать в этот класс параметры, которые могут выступать в качестве константы в вашем уравнении, поэтому, если один из ваших внутренних классов выглядит следующим образом:
f(x,y)=x*y
но иногда вам нужен такой, который:
f(x,y)=x*y*2
и, возможно, третье, что:
f(x,y)=x*y/2
вместо того, чтобы создавать два анонимных внутренних класса или добавлять параметр "passthrough", вы можете создать отдельный класс 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);
Но неизменяемые классы безопаснее - я не могу придумать оправдания, чтобы сделать такой класс изменчивым.
Я действительно публикую это только потому, что сжимаюсь всякий раз, когда слышу анонимный внутренний класс - я видел много избыточного кода, который был "Обязательным", потому что первое, что сделал программист, это стало анонимным, когда он должен был использовать реальный класс и никогда переосмыслил свое решение.
Одна из вещей, которые мне действительно не хватает при программировании на Java - это обратные вызовы функций. Одной из ситуаций, когда необходимость в них постоянно присутствовала, была рекурсивная обработка иерархий, когда вы хотите выполнить какое-то конкретное действие для каждого элемента. Например, обход дерева каталогов или обработка структуры данных. Минималист внутри меня ненавидит необходимость определять интерфейс, а затем реализацию для каждого конкретного случая.
Однажды я подумал: а почему бы и нет? У нас есть указатели на метод - объект Method. С оптимизацией JIT-компиляторов рефлексивный вызов больше не влечет за собой огромных потерь производительности. И помимо, скажем, копирования файла из одного местоположения в другое, стоимость отраженного вызова метода бледнеет.
Когда я больше думал об этом, я понял, что обратный вызов в парадигме ООП требует связывания объекта и метода вместе - введите объект обратного вызова.
Проверьте мое решение на основе отражения для обратных вызовов в Java. Бесплатно для любого использования.
Чтобы сделать то же самое без интерфейсов для массива функций:
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); } }
Похоже, шаблон стратегии для меня. Проверьте fluffycat.com шаблоны Java.
Хорошо, эта ветка уже достаточно старая, поэтому, скорее всего, мой ответ не поможет в этом вопросе. Но поскольку эта ветка помогла мне найти решение, я все равно выложу его здесь.
Мне нужно было использовать переменный статический метод с известным вводом и известным выводом (оба double). Итак, зная пакет метода и имя, я мог бы работать следующим образом:
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, но в настоящее время я достаточно доволен тем, как оно есть. Три строки кода, без классов и интерфейсов, это не так уж плохо.
Проверьте лямбдай
http://code.google.com/p/lambdaj/
и, в частности, его новая функция закрытия
http://code.google.com/p/lambdaj/wiki/Closures
и вы найдете очень удобочитаемый способ определения замыкания или указателя на функцию без создания бессмысленного интерфейса или использования некрасивых внутренних классов
Вау, почему бы просто не создать класс Delegate, который не так уж и сложен, учитывая то, что я уже сделал для java, и использовать его для передачи параметра, где T - тип возвращаемого значения. Прошу прощения, но как программист на C++/C#, который только изучает Java, мне нужны указатели на функции, потому что они очень удобны. Если вы знакомы с любым классом, который имеет дело с информацией о методе, вы можете это сделать. В Java-библиотеках это будет java.lang.reflect.method.
Если вы всегда используете интерфейс, вы всегда должны реализовать его. В обработке событий на самом деле нет лучшего способа регистрации / отмены регистрации из списка обработчиков, но для делегатов, в которых нужно передавать функции, а не тип значения, создание класса делегата для его обработки для превосходящих интерфейс.
Ни один из ответов по Java 8 не дал полного, связного примера, так что вот оно.
Объявите метод, который принимает "указатель на функцию" следующим образом:
void doCalculation(Function<Integer, String> calculation, int parameter) {
final String result = calculation.apply(parameter);
}
Вызовите его, предоставив функции лямбда-выражение:
doCalculation((i) -> i.toString(), 2);
До 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);
Начиная с Java8, вы можете использовать лямбды, которые также имеют библиотеки в официальном API SE 8.
Использование: Вам нужно использовать интерфейс только с одним абстрактным методом. Сделайте экземпляр этого (вы можете использовать один из уже предоставленных java SE 8) следующим образом:
Function<InputType, OutputType> functionname = (inputvariablename) {
...
return outputinstance;
}
Для получения дополнительной информации ознакомьтесь с документацией: https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html
Если кто-то изо всех сил пытается передать функцию, которая принимает один набор параметров для определения своего поведения, но другой набор параметров для выполнения, например, схемы:
(define (function scalar1 scalar2)
(lambda (x) (* x scalar1 scalar2)))
см. функцию передачи с определенным параметром поведением в Java
Проект безопасного зеркала с открытым исходным кодом обобщает некоторые из вышеупомянутых решений в библиотеке, которая добавляет функции, делегаты и события в Java.
См. README или этот ответ stackru для ознакомления с чит-листом функций.
Что касается функций, библиотека представляет интерфейс Fun и некоторые субинтерфейсы, которые (вместе с универсальными шаблонами) составляют свободный API для использования методов в качестве типов.
Fun.With0Params<String> myFunctionField = " hello world "::trim;`
Fun.With2Params<Boolean, Object, Object> equals = Objects::equals;`
public void foo(Fun.With1ParamAndVoid<String> printer) throws Exception {
printer.invoke("hello world);
}
public void test(){
foo(System.out::println);
}
Уведомление:
- что вы должны выбрать субинтерфейс, который соответствует количеству параметров в целевой подписи. Fx, если у него один параметр, выберите Fun.With1Param.
- что Generics используются для определения A) возвращаемого типа и B) параметров подписи.
Также обратите внимание, что сигнатура ссылки на метод, передаваемой при вызове метода foo(), должна соответствовать Fun, определенному методом Foo. В противном случае компилятор выдаст ошибку.