ตัวทดแทนฟังก์ชันที่ใกล้ที่สุดใน Java คืออะไร


299

ฉันมีวิธีที่เกี่ยวกับโค้ดสิบบรรทัด ฉันต้องการสร้างวิธีการเพิ่มเติมที่ทำสิ่งเดียวกันยกเว้นการคำนวณขนาดเล็กที่จะเปลี่ยนรหัสหนึ่งบรรทัด นี่เป็นแอปพลิเคชั่นที่สมบูรณ์แบบสำหรับการส่งผ่านตัวชี้ฟังก์ชันเพื่อแทนที่หนึ่งบรรทัด แต่ Java ไม่มีตัวชี้ฟังก์ชัน ทางเลือกที่ดีที่สุดของฉันคืออะไร


5
Java 8 จะมีแลมบ์ดานิพจน์ คุณสามารถอ่านเพิ่มเติมเกี่ยวกับการแสดงออกแลมบ์ดานี่
Marius

4
@Marius ฉันไม่คิดว่าการแสดงออกแลมบ์ดานับเป็นตัวชี้ฟังก์ชั่น ในทางกลับกัน::ผู้ประกอบการ ...
The Guy with The Hat

ขออภัยสำหรับความคิดเห็นที่ล่าช้า;) - โดยปกติคุณไม่จำเป็นต้องมีตัวชี้ฟังก์ชันสำหรับสิ่งนั้น เพียงใช้วิธีเทมเพลต! ( en.wikipedia.org/wiki/Template_method_pattern )
isnot2bad

2
@ isnot2bad - ดูที่บทความดูเหมือน overkill - ซับซ้อนกว่าคำตอบที่ให้ไว้ที่นี่ โดยเฉพาะวิธีแม่แบบต้องสร้างคลาสย่อยสำหรับแต่ละการคำนวณทางเลือก ฉันไม่เห็นมีอะไร OP ระบุว่าต้องsubclasses ; เขาเพียงต้องการสร้างวิธีการต่างๆ และแบ่งปันการดำเนินการส่วนใหญ่ ตามคำตอบที่ยอมรับแสดงว่าสิ่งนี้ทำได้ง่าย "ในตำแหน่ง" (ภายในแต่ละวิธี) แม้กระทั่งก่อนหน้า Java 8 ที่มี lambdas
ToolmakerSteve

@ToolmakerSteve โซลูชันที่ได้รับการยอมรับยังต้องการในคลาสต่อการคำนวณ (แม้ว่าจะเป็นเพียงคลาสภายในที่ไม่ระบุชื่อ) และรูปแบบเมธอดเท็มเพลตสามารถรับรู้ได้โดยใช้คลาสภายในแบบไม่ระบุชื่อดังนั้นจึงไม่แตกต่างกันมากจากโซลูชันที่ยอมรับซึ่งเกี่ยวข้องกับโอเวอร์เฮด (ก่อนหน้า Java 8) ดังนั้นจึงเป็นคำถามเพิ่มเติมเกี่ยวกับรูปแบบการใช้งานและข้อกำหนดด้าน detailled ซึ่งเราไม่ทราบ ฉันซาบซึ้งกับคำตอบที่ยอมรับและเพียงต้องการเพิ่มความเป็นไปได้ที่จะคิดอีกครั้ง
isnot2bad

คำตอบ:


269

ชั้นในที่ไม่ระบุชื่อ

สมมติว่าคุณต้องการที่จะมีฟังก์ชั่นการส่งผ่านด้วยพารามิเตอร์ที่ส่งกลับ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);

13
นี่คือตัวอย่างของ "Command Patern" โดยวิธีการ en.wikipedia.org/wiki/Command_Pattern
Ogre Psalm33

3
@Ogre Psalm33 เทคนิคนี้อาจเป็นรูปแบบกลยุทธ์ขึ้นอยู่กับว่าคุณใช้มันอย่างไร ความแตกต่างระหว่างรูปแบบกลยุทธ์และรูปแบบคำสั่ง
Rory O'Kane

2
นี่คือการดำเนินการปิดสำหรับ Java 5, 6 และ 7 mseifed.blogspot.se/2012/09/… มันมีสิ่งที่ทุกคนสามารถขอได้ ... ฉันคิดว่ามันยอดเยี่ยมมาก!
mmm

@SecretService: ลิงก์นั้นตาย
Lawrence Dol

@LawrenceDol ใช่มันเป็น นี่คือ pastebin ของชั้นเรียนที่ฉันใช้ pastebin.com/b1j3q2Lp
mmm

32

สำหรับ "ตัวชี้ฟังก์ชั่น" แต่ละอันฉันจะสร้างคลาส functorขนาดเล็กที่ใช้การคำนวณของคุณ กำหนดอินเทอร์เฟซที่ทุกคลาสจะนำไปใช้และส่งอินสแตนซ์ของวัตถุเหล่านั้นไปยังฟังก์ชันที่ใหญ่กว่าของคุณ นี่คือการรวมกันของ " รูปแบบคำสั่ง " และ " รูปแบบกลยุทธ์ "

@ ตัวอย่างของ sblundy นั้นดี


28

เมื่อมีการคำนวณที่แตกต่างกันจำนวนหนึ่งที่กำหนดไว้ล่วงหน้าคุณสามารถทำได้ในหนึ่งบรรทัดการใช้ 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);
}

เห็นได้ชัดว่าการประกาศวิธีการกลยุทธ์เช่นเดียวกับหนึ่งอินสแตนซ์ของการใช้งานแต่ละอย่างจะถูกกำหนดไว้ในคลาส / ไฟล์เดียว


24

คุณต้องสร้างส่วนต่อประสานที่มีฟังก์ชั่นที่คุณต้องการผ่าน เช่น:

/**
 * 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 แทนอินเทอร์เฟซของคุณเองได้หากคุณไม่ต้องการส่งผ่านพารามิเตอร์หรือคุณสามารถใช้เทคนิคอื่น ๆ เพื่อทำให้พารามิเตอร์นับน้อยลง "คงที่" แต่โดยทั่วไปแล้วจะเป็นการแลกเปลี่ยนกับความปลอดภัยของประเภท (หรือคุณสามารถแทนที่ Constructor เพื่อให้อ็อพเจ็กต์ฟังก์ชั่นของคุณส่งผ่าน params ในแบบที่ว่า .. มีวิธีการมากมายและทำงานได้ดีขึ้นในบางสถานการณ์)


4
นี้“คำตอบ” ที่เกี่ยวข้องมากขึ้นในชุดปัญหากว่ากับชุดโซลูชั่น
tchrist

18

วิธีการอ้างอิงโดยใช้::โอเปอเรเตอร์

คุณสามารถใช้การอ้างอิงวิธีการในการขัดแย้งวิธีการที่วิธีการที่ยอมรับอินเตอร์เฟซที่ใช้งานได้ ส่วนต่อประสานการทำงานคือส่วนต่อประสานใด ๆ ที่มีเพียงวิธีนามธรรม (อินเทอร์เฟซที่ใช้งานได้อาจมีหนึ่งหรือมากกว่าหนึ่งวิธีเริ่มต้นหรือวิธีการคงที่)

IntBinaryOperatorเป็นส่วนต่อประสานการทำงาน วิธีนามธรรมมันapplyAsIntยอมรับสองints intเป็นพารามิเตอร์และผลตอบแทนของมัน Math.maxยังยอมรับสองและส่งกลับint intในตัวอย่างนี้A.method(Math::max);ทำให้parameter.applyAsIntส่งสองค่าปัจจัยการผลิตไปและกลับผลมาจากการที่Math.maxMath.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);
    }
});

สำหรับข้อมูลเพิ่มเติมโปรดดูที่:: (ลำไส้ใหญ่คู่) ผู้ประกอบการในชวา 8และJava Language ข้อกำหนด§15.13


15

คุณยังสามารถทำสิ่งนี้ได้ (ซึ่งในบางครั้ง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);
    }
}

1
ฉันใช้สิ่งนี้สำหรับการจัดการเมนู / GUI บางครั้งเนื่องจากไวยากรณ์ของเมธอดนั้นง่ายกว่าไวยากรณ์ของคลาสภายในที่ไม่ระบุชื่อ มันเรียบร้อย แต่คุณกำลังเพิ่มความซับซ้อนของการสะท้อนซึ่งบางคนไม่ต้องการที่จะขุดดังนั้นให้แน่ใจว่าคุณทำให้ถูกต้องและมีข้อผิดพลาดที่ชัดเจนและเป็นข้อความสำหรับทุกข้อผิดพลาดที่เป็นไปได้
Bill K

คุณสามารถพิมพ์ได้อย่างปลอดภัยโดยใช้ยาชื่อสามัญและคุณไม่จำเป็นต้องไตร่ตรอง
Luigi Plinge

1
ฉันล้มเหลวที่จะเห็นว่าการใช้ยาชื่อสามัญและการไม่ใช้การสะท้อนจะช่วยให้คุณเรียกวิธีการด้วยชื่อที่อยู่ในสตริงได้หรือไม่
TofuBeer

1
@LuigiPlinge - คุณสามารถให้ข้อมูลโค้ดของสิ่งที่คุณหมายถึงอะไร
ToolmakerSteve

13

หากคุณมีเพียงหนึ่งบรรทัดซึ่งแตกต่างกันคุณสามารถเพิ่มพารามิเตอร์เช่นคำสั่ง flag และคำสั่ง if (flag) ซึ่งเรียกหนึ่งบรรทัดหรืออีกบรรทัดหนึ่ง


1
คำตอบของ javaslook ดูเหมือนจะเป็นวิธีที่สะอาดกว่าหากทำมากกว่าสองตัวแปรการคำนวณ หรือหากมีความประสงค์ที่จะฝังโค้ดลงในเมธอดดังนั้น enum สำหรับกรณีต่าง ๆ ซึ่งเมธอดนั้นจัดการและสวิตช์
ToolmakerSteve

1
@ToolmakerSteve จริง แต่วันนี้คุณจะใช้ lambdas ใน Java 8
Peter Lawrey

12

คุณอาจสนใจที่จะได้ยินเกี่ยวกับการทำงานที่เกิดขึ้นกับ Java 7 เกี่ยวกับการปิด:

สถานะการปิดปัจจุบันใน Java คืออะไร

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


+1 สำหรับลิงก์ที่มีประโยชน์แม้ว่าฉันคิดว่าการเพิ่มการปิดไปยัง Java นั้นไม่ช่วยเหลืออย่างสมบูรณ์
mikera

11

ใหม่อินเทอร์เฟซสำหรับการทำงานของ 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();
    }
}

แล้วเรามีอะไรอยู่ที่นี่?

  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 รุ่นก่อนหน้านี้คุณควรลอง Guava Libraries ซึ่งมีฟังก์ชันการทำงานและไวยากรณ์เหมือนกันตามที่ Adrian Petrescu ได้กล่าวไว้ข้างต้น

สำหรับการวิจัยเพิ่มเติมดูที่Java 8 Cheatsheet

และขอขอบคุณ The Guy with The Hat สำหรับลิงก์ข้อกำหนดภาษา Java §15.13


7
" เพราะสิ่งอื่น ๆ ... ไม่ดีสำหรับการอ่านโค้ด " เป็นการอ้างสิทธิ์ที่ไม่มีมูลความจริงอย่างสมบูรณ์และผิดนอกจากนี้
Lawrence Dol

9

คำตอบ @ 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);

แต่คลาสที่ไม่เปลี่ยนรูปนั้นปลอดภัยกว่า - ฉันไม่สามารถหาเหตุผลมาสร้างคลาสแบบนี้ได้

ฉันโพสต์สิ่งนี้เพียงเพราะฉันประจบประแจงเมื่อใดก็ตามที่ฉันได้ยินชั้นในแบบไม่ระบุชื่อ - ฉันเห็นรหัสซ้ำซ้อนจำนวนมากที่เป็น "จำเป็น" เพราะสิ่งแรกที่โปรแกรมเมอร์ทำก็ไม่ระบุชื่อเมื่อเขาควรใช้คลาสจริงและไม่เคย คิดใหม่การตัดสินใจของเขา


ฮะ? OP กำลังพูดถึงการคำนวณที่แตกต่างกัน(อัลกอริทึมตรรกะ); คุณกำลังแสดงค่าที่แตกต่างกัน(ข้อมูล) คุณแสดงกรณีเฉพาะที่สามารถรวมความแตกต่างเป็นค่าได้ แต่นั่นเป็นการทำให้เข้าใจง่ายของปัญหาที่เกิดขึ้น
ToolmakerSteve

6

ของ Google ห้องสมุดฝรั่งซึ่งจะกลายเป็นที่นิยมมากมีทั่วไปฟังก์ชั่นและคำกริยาวัตถุที่พวกเขาได้ทำงานในหลาย ๆ ส่วนของ API ของพวกเขา


คำตอบนี้จะมีประโยชน์มากขึ้นถ้ามันให้รายละเอียดรหัส ใช้รหัสที่แสดงในคำตอบที่ยอมรับและแสดงให้เห็นว่าจะมีลักษณะอย่างไรเมื่อใช้ฟังก์ชัน
ToolmakerSteve


4

สิ่งหนึ่งที่ฉันพลาดจริง ๆ เมื่อเขียนโปรแกรมใน Java คือการเรียกกลับฟังก์ชัน สถานการณ์หนึ่งที่ความต้องการในการนำเสนอเหล่านี้ยังคงอยู่ในการประมวลผลลำดับชั้นซ้ำซึ่งคุณต้องการดำเนินการบางอย่างสำหรับแต่ละรายการ เช่นเดียวกับการเดินแผนผังไดเรกทอรีหรือประมวลผลโครงสร้างข้อมูล ความเรียบง่ายในตัวฉันเกลียดที่จะต้องกำหนดอินเทอร์เฟซแล้วนำไปใช้กับแต่ละกรณี

วันหนึ่งฉันพบว่าตัวเองสงสัยว่าทำไมไม่ เรามีตัวชี้วิธี - วัตถุวิธี ด้วยการปรับแต่งคอมไพเลอร์ของ JIT การเรียกใช้ไตร่ตรองแบบไตร่ตรองจะไม่ส่งผลต่อประสิทธิภาพที่ยิ่งใหญ่อีกต่อไป และนอกเหนือจากนั้นกล่าวคือการคัดลอกไฟล์จากที่หนึ่งไปยังอีกที่หนึ่งค่าใช้จ่ายของการเรียกใช้เมธอดที่สะท้อนจะลดลงอย่างไม่มีนัยสำคัญ

เมื่อฉันคิดถึงเรื่องนี้มากขึ้นฉันก็ตระหนักว่าการโทรกลับในกระบวนทัศน์ของ OOP นั้นต้องผูกวัตถุและวิธีเข้าด้วยกัน - ใส่วัตถุโทรกลับ

ตรวจสอบแก้ปัญหาพื้นฐานของฉันสะท้อนCallbacks ในชวา ฟรีสำหรับการใช้งานใด ๆ


4

oK กระทู้นี้เก่าแล้วพอดังนั้นคำตอบของฉันอาจไม่เป็นประโยชน์สำหรับคำถาม แต่เนื่องจากหัวข้อนี้ช่วยให้ฉันหาวิธีแก้ปัญหาของฉันฉันจะใส่มันที่นี่ต่อไป

ฉันจำเป็นต้องใช้วิธีการคงที่ตัวแปรที่มีการป้อนข้อมูลที่รู้จักและการส่งออกที่รู้จัก (ทั้งสองครั้ง ) ดังนั้นเมื่อรู้วิธีการแพคเกจและชื่อฉันสามารถทำงานดังต่อไปนี้:

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);

โดยที่ activity เป็นค่าสองเท่าโดยพลการ ฉันคิดว่าอาจจะทำสิ่งนี้ให้เป็นนามธรรมมากกว่าและทำให้เป็นเรื่องทั่วไปตามที่ SoftwareMonkey ได้ทำไปแล้ว แต่ปัจจุบันฉันมีความสุขกับวิธีการที่เป็นอยู่ โค้ดสามบรรทัดไม่มีคลาสและอินเทอร์เฟซที่จำเป็นนั่นก็ไม่ได้แย่เกินไป


ขอบคุณ Rob สำหรับการเพิ่มcodemarkdown ฉันก็ใจร้อนและโง่เกินกว่าจะหามันได้ ;-)
yogibimbi

4

ในการทำสิ่งเดียวกันโดยไม่ต้องมีอินเตอร์เฟสสำหรับฟังก์ชันอาร์เรย์:

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); } }

1
ทำไม? สิ่งนี้ซับซ้อนกว่าโซลูชันที่เสนอไว้ก่อนหน้านี้ซึ่งต้องการเพียงนิยามฟังก์ชันแบบไม่ระบุชื่อหนึ่งตัวต่อหนึ่งตัวเลือก คุณสร้างคลาสและนิยามฟังก์ชันที่ไม่ระบุชื่อตามตัวเลือก ยิ่งแย่ไปกว่านั้นสามารถทำได้ในสองตำแหน่งที่แตกต่างกันในรหัส คุณอาจต้องการให้เหตุผลบางอย่างสำหรับการใช้วิธีนี้
ToolmakerSteve

3

ลองดู lambdaj

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

และโดยเฉพาะอย่างยิ่งคุณสมบัติการปิดใหม่

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

และคุณจะพบวิธีที่สามารถอ่านได้อย่างชัดเจนในการกำหนดการปิดหรือตัวชี้ฟังก์ชั่นโดยไม่ต้องสร้างอินเตอร์เฟสที่ไม่มีความหมายหรือใช้คลาสภายในที่น่าเกลียด


3

ว้าวทำไมไม่เพียงแค่สร้างคลาส Delegate ซึ่งไม่ใช่ทั้งหมดที่ยากสำหรับฉันแล้วสำหรับ java และใช้มันเพื่อส่งผ่านพารามิเตอร์ที่ T เป็นชนิดส่งคืน ฉันขอโทษ แต่ในฐานะที่เป็นโปรแกรมเมอร์ C ++ / C # โดยทั่วไปเพียงแค่เรียนรู้จาวาฉันต้องการตัวชี้ฟังก์ชั่นเพราะมันมีประโยชน์มาก หากคุณคุ้นเคยกับคลาสใด ๆ ที่เกี่ยวข้องกับวิธีการข้อมูลคุณสามารถทำได้ ในไลบรารี java ที่จะเป็น java.lang.reflect.method

หากคุณใช้อินเทอร์เฟซคุณจะต้องนำไปใช้เสมอ ในการจัดการเหตุการณ์มีวิธีที่ดีกว่าในการลงทะเบียน / ไม่ลงทะเบียนจากรายการตัวจัดการ แต่สำหรับผู้ร่วมประชุมที่คุณต้องส่งผ่านฟังก์ชั่นและไม่ใช่ประเภทของค่าการสร้างคลาสตัวแทนเพื่อจัดการกับคลาส


ไม่ใช่คำตอบที่เป็นประโยชน์เว้นแต่คุณจะแสดงรายละเอียดของรหัส การสร้างคลาส Delegate ช่วยได้อย่างไร จำเป็นต้องใช้รหัสใดต่อตัวเลือกอื่น
ToolmakerSteve

2

ไม่มีคำตอบของ Java 8 ที่ให้ตัวอย่างที่สมบูรณ์และเหนียวแน่นดังนั้นที่นี่จึงมาถึง

ประกาศวิธีที่ยอมรับ "function pointer" ดังนี้:

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

เรียกมันว่าโดยการให้ฟังก์ชั่นด้วยการแสดงออกแลมบ์ดา:

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

1

หากทุกคนกำลังดิ้นรนที่จะผ่านฟังก์ชั่นที่ใช้พารามิเตอร์หนึ่งชุดเพื่อกำหนดพฤติกรรมของมัน แต่พารามิเตอร์อีกชุดหนึ่งที่จะดำเนินการเช่น Scheme:

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

ดูฟังก์ชั่นการส่งผ่านที่มีพฤติกรรมที่กำหนดพารามิเตอร์ใน Java


1

ตั้งแต่ Java8 คุณสามารถใช้ lambdas ซึ่งมีไลบรารีใน SE8 API อย่างเป็นทางการ

การใช้งาน: คุณต้องใช้อินเทอร์เฟซด้วยวิธีนามธรรมเพียงวิธีเดียว ทำอินสแตนซ์ของมัน (คุณอาจต้องการใช้หนึ่ง java SE 8 ที่จัดเตรียมไว้แล้ว) เช่นนี้:

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

สำหรับการตรวจสอบข้อมูลเพิ่มเติมเกี่ยวกับเอกสาร: https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html


1

ก่อนหน้า 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);
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.