Apa pengganti terdekat untuk pointer fungsi di Jawa?


299

Saya punya metode yang sekitar sepuluh baris kode. Saya ingin membuat lebih banyak metode yang melakukan hal yang persis sama, kecuali untuk perhitungan kecil yang akan mengubah satu baris kode. Ini adalah aplikasi yang sempurna untuk melewatkan pointer fungsi untuk mengganti satu baris itu, tetapi Java tidak memiliki pointer fungsi. Apa alternatif terbaik saya?


5
Java 8 akan memiliki Ekspresi Lambda . Anda dapat membaca lebih lanjut tentang ekspresi lambda di sini .
Marius

4
@Marius Saya tidak berpikir ekspresi lambda dianggap sebagai pointer fungsi. The ::operator, di sisi lain ...
The Guy dengan The Hat

Maaf atas komentar yang terlambat;) - biasanya, Anda tidak memerlukan pointer fungsi untuk itu. Cukup gunakan metode templat! ( en.wikipedia.org/wiki/Template_method_pattern )
isnot2bad

2
@ isnot2bad - melihat artikel itu, sepertinya berlebihan - lebih kompleks daripada jawaban yang diberikan di sini. Secara khusus, metode templat membutuhkan pembuatan subkelas untuk setiap perhitungan alternatif. Saya tidak melihat OP telah menyatakan sesuatu yang membutuhkan subclass ; dia hanya ingin membuat beberapa metode , dan berbagi sebagian besar implementasinya. Seperti yang ditunjukkan oleh jawaban yang diterima, ini mudah dilakukan "di tempat" (di dalam setiap metode), bahkan sebelum Jawa 8 dengan lambda-nya.
ToolmakerSteve

@ToolmakerSteve Solusi yang diterima juga membutuhkan pada kelas per perhitungan (bahkan jika itu hanya kelas dalam anonim). Dan pola metode templat juga dapat direalisasikan menggunakan kelas dalam anonim, sehingga tidak jauh berbeda dari solusi yang diterima tentang overhead (sebelum Java 8). Jadi ini lebih merupakan pertanyaan tentang pola penggunaan dan persyaratan tetas, yang kita tidak tahu. Saya menghargai jawaban yang diterima dan hanya ingin menambahkan kemungkinan lain untuk dipikirkan.
isnot2bad

Jawaban:


269

Kelas dalam anonim

Katakanlah Anda ingin memiliki fungsi yang diteruskan dengan Stringparam yang mengembalikan sebuah int.
Pertama, Anda harus mendefinisikan antarmuka dengan fungsi sebagai satu-satunya anggota, jika Anda tidak dapat menggunakan kembali yang sudah ada.

interface StringFunction {
    int func(String param);
}

Metode yang mengambil pointer hanya akan menerima StringFunctioncontoh seperti:

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

Dan akan disebut seperti ini:

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

EDIT: Di Java 8, Anda bisa menyebutnya dengan ekspresi lambda:

ref.takingMethod(param -> bodyExpression);

13
Ini adalah contoh dari "Command Patern", omong-omong. en.wikipedia.org/wiki/Command_Pattern
Ogre Psalm33

3
@Ogre Psalm33 Teknik ini juga bisa menjadi Pola Strategi, tergantung pada bagaimana Anda menggunakannya. Perbedaan antara Pola Strategi dan Pola Perintah .
Rory O'Kane

2
Berikut ini adalah implementasi penutupan untuk Java 5, 6, dan 7 mseifed.blogspot.se/2012/09/… Ini berisi semua orang bisa meminta ... Saya pikir ini sangat mengagumkan!
mmm

@SecretService: Tautan itu sudah mati.
Lawrence Dol

@LawrenceDol Ya, benar. Ini adalah pastebin dari kelas yang saya gunakan. pastebin.com/b1j3q2Lp
mmm

32

Untuk setiap "pointer fungsi", saya akan membuat kelas functor kecil yang mengimplementasikan perhitungan Anda. Tentukan antarmuka yang akan diimplementasikan oleh semua kelas, dan berikan instance objek-objek itu ke fungsi Anda yang lebih besar. Ini adalah kombinasi dari " pola perintah ", dan " pola strategi ".

@ sblundy contohnya bagus.


28

Ketika ada sejumlah perhitungan berbeda yang telah ditentukan yang dapat Anda lakukan dalam satu baris itu, menggunakan enum adalah cara yang cepat namun jelas untuk menerapkan pola strategi.

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

Jelas, deklarasi metode strategi, serta tepat satu contoh dari setiap implementasi semua didefinisikan dalam satu kelas / file.


24

Anda perlu membuat antarmuka yang menyediakan fungsi yang ingin Anda sampaikan. misalnya:

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

}

Kemudian, ketika Anda harus melewati suatu fungsi, Anda bisa mengimplementasikan antarmuka itu:

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

Akhirnya, fungsi peta menggunakan yang diteruskan di Function1 sebagai berikut:

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

Anda sering dapat menggunakan Runnable sebagai ganti antarmuka Anda sendiri jika Anda tidak perlu memasukkan parameter, atau Anda dapat menggunakan berbagai teknik lain untuk membuat jumlah param lebih sedikit "diperbaiki" tetapi biasanya merupakan pertukaran dengan keamanan jenis. (Atau Anda dapat menimpa konstruktor untuk objek fungsi Anda untuk lulus dalam params dengan cara itu .. ada banyak pendekatan, dan beberapa bekerja lebih baik dalam keadaan tertentu.)


4
"Jawaban" ini lebih berkaitan dengan set masalah daripada set solusi.
tchrist

18

Referensi metode menggunakan ::operator

Anda dapat menggunakan referensi metode dalam argumen metode di mana metode menerima antarmuka fungsional . Antarmuka fungsional adalah semua antarmuka yang hanya berisi satu metode abstrak. (Antarmuka fungsional dapat berisi satu atau lebih metode standar atau metode statis.)

IntBinaryOperatoradalah antarmuka fungsional. Metode abstraknya applyAsInt,, menerima dua ints sebagai parameternya dan mengembalikan sebuah int. Math.maxjuga menerima dua intdan mengembalikan sebuah int. Dalam contoh ini, A.method(Math::max);membuat parameter.applyAsIntkirim dua nilai inputnya Math.maxdan kembalikan hasilnya 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);
    }
}

Secara umum, Anda dapat menggunakan:

method1(Class1::method2);

dari pada:

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

yang merupakan kependekan dari:

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

Untuk informasi lebih lanjut, lihat :: operator (titik dua) di Java 8 dan Spesifikasi Bahasa Java §15.13 .


15

Anda juga dapat melakukan ini (yang dalam beberapa kesempatan RARE masuk akal). Masalahnya (dan ini adalah masalah besar) adalah Anda kehilangan semua jenis keamanan menggunakan kelas / antarmuka dan Anda harus berurusan dengan kasus di mana metode ini tidak ada.

Itu memang memiliki "manfaat" bahwa Anda dapat mengabaikan pembatasan akses dan memanggil metode pribadi (tidak ditampilkan dalam contoh, tetapi Anda dapat memanggil metode yang biasanya tidak dikompilasi oleh kompiler).

Sekali lagi, ini adalah kasus langka yang masuk akal, tetapi pada kesempatan itu adalah alat yang bagus untuk dimiliki.

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
Saya menggunakan ini untuk penanganan menu / GUI kadang-kadang karena sintaks metode jauh lebih sederhana daripada sintaksis kelas dalam anonim. Rapi, tetapi Anda menambahkan kompleksitas refleksi yang tidak ingin digali oleh beberapa orang, jadi pastikan Anda melakukannya dengan benar dan memiliki kesalahan teks yang jelas untuk setiap kondisi kesalahan yang mungkin terjadi.
Bill K

Anda dapat mengetikkannya dengan aman menggunakan obat generik dan Anda tidak perlu refleksi.
Luigi Plinge

1
Saya gagal melihat bagaimana menggunakan obat generik dan tidak menggunakan refleksi akan membiarkan Anda memanggil metode dengan nama yang terkandung dalam String?
TofuBeer

1
@LuigiPlinge - dapatkah Anda memberikan cuplikan kode yang Anda maksud?
ToolmakerSteve

13

Jika Anda hanya memiliki satu baris yang berbeda, Anda dapat menambahkan parameter seperti flag dan pernyataan if (flag) yang memanggil satu baris atau yang lainnya.


1
Jawaban javaslook sepertinya cara yang lebih bersih untuk melakukan ini, jika lebih dari dua varian penghitungan. Atau jika seseorang ingin menanamkan kode ke dalam metode, maka enum untuk kasus-kasus berbeda yang ditangani oleh metode, dan sebuah saklar.
ToolmakerSteve

1
@ToolmakerSteve benar, meskipun hari ini Anda akan menggunakan lambdas di Jawa 8.
Peter Lawrey


11

Java 8 Interface Fungsional dan Referensi Metode menggunakan ::operator.

Java 8 mampu mempertahankan referensi metode (MyClass :: baru) dengan " @Functional Interface pointer "@Functional ". Tidak perlu untuk nama metode yang sama, hanya tanda tangan metode yang sama diperlukan.

Contoh:

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

Jadi, apa yang kita miliki di sini?

  1. Antarmuka Fungsional - ini adalah antarmuka, beranotasi atau tidak dengan @FunctionalInterface , yang hanya berisi satu deklarasi metode.
  2. Referensi Metode - ini hanya sintaks khusus, terlihat seperti ini, objectInstance :: methodName , tidak lebih tidak kurang.
  3. Contoh penggunaan - hanya operator penugasan dan kemudian memanggil metode antarmuka.

ANDA HARUS MENGGUNAKAN FUNGSI FUNGSIONAL UNTUK MENDENGARKAN HANYA DAN HANYA UNTUK ITU!

Karena semua fungsi pointer seperti itu benar-benar buruk untuk keterbacaan kode dan kemampuan untuk memahami. Namun, referensi metode langsung terkadang berguna, dengan foreach misalnya.

Ada beberapa Antarmuka Fungsional yang telah ditentukan:

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

Untuk versi Java yang lebih lama Anda harus mencoba Guava Libraries, yang memiliki fungsi dan sintaksis yang sama, seperti yang telah disebutkan oleh Adrian Petrescu di atas.

Untuk penelitian tambahan, lihat Java 8 Cheatsheet

dan terima kasih kepada The Guy with The Hat untuk Spesifikasi Bahasa Jawa §15.13 tautan.


7
" Karena semua yang lain ... benar-benar buruk untuk keterbacaan kode " adalah klaim yang sama sekali tidak berdasar, dan salah.
Lawrence Dol

9

@ sblundy adalah jawaban yang bagus, tetapi kelas-kelas dalam anonim memiliki dua kelemahan kecil, yang utama adalah bahwa mereka cenderung tidak dapat digunakan kembali dan yang kedua adalah sintaksis yang besar.

Yang menyenangkan adalah polanya berkembang menjadi kelas penuh tanpa perubahan di kelas utama (yang melakukan perhitungan).

Saat Anda membuat instance kelas baru Anda bisa mengirimkan parameter ke kelas itu yang dapat bertindak sebagai konstanta dalam persamaan Anda - jadi jika salah satu kelas dalam Anda terlihat seperti ini:

f(x,y)=x*y

tetapi terkadang Anda membutuhkannya yaitu:

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

dan mungkin yang ketiga yaitu:

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

Daripada membuat dua kelas dalam anonim atau menambahkan parameter "passthrough", Anda bisa membuat kelas ACTUAL tunggal yang Anda instantiate sebagai:

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

Itu hanya akan menyimpan konstanta di kelas dan menggunakannya dalam metode yang ditentukan dalam antarmuka.

Bahkan, jika TAHU bahwa fungsi Anda tidak akan disimpan / digunakan kembali, Anda bisa melakukan ini:

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

Tapi kelas yang tidak bisa diubah lebih aman - saya tidak bisa memberikan pembenaran untuk membuat kelas seperti ini bisa berubah.

Saya benar-benar hanya memposting ini karena saya merasa ngeri setiap kali saya mendengar kelas batin anonim - Saya telah melihat banyak kode berlebihan yang "Diperlukan" karena hal pertama yang dilakukan programmer adalah pergi anonim ketika ia seharusnya menggunakan kelas aktual dan tidak pernah memikirkan kembali keputusannya.


Hah? OP berbicara tentang perhitungan yang berbeda (algoritma; logika); Anda menunjukkan nilai (data) yang berbeda. Anda memang menunjukkan kasus tertentu di mana perbedaan dapat dimasukkan ke dalam nilai, tetapi itu adalah penyederhanaan yang tidak dapat dibenarkan atas masalah yang diajukan.
ToolmakerSteve

6

Pustaka Google Guava , yang menjadi sangat populer, memiliki fungsi dan predikat objek generik yang telah mereka kerjakan ke banyak bagian API mereka.


Jawaban ini akan lebih bermanfaat jika memberikan detail kode. Ambil kode yang ditunjukkan pada jawaban yang diterima, dan perlihatkan bagaimana tampilannya menggunakan Function.
ToolmakerSteve

4

Kedengarannya seperti pola strategi bagi saya. Lihat pola Java fluffycat.com.


4

Salah satu hal yang saya sangat rindukan ketika pemrograman di Jawa adalah fungsi callback. Satu situasi di mana kebutuhan untuk ini terus muncul dengan sendirinya adalah dalam memproses hierarki secara rekursif di mana Anda ingin melakukan beberapa tindakan spesifik untuk setiap item. Seperti berjalan di pohon direktori, atau memproses struktur data. Minimalis di dalam diriku benci harus mendefinisikan antarmuka dan kemudian implementasi untuk setiap kasus tertentu.

Suatu hari saya mendapati diri saya bertanya-tanya mengapa tidak? Kami memiliki pointer metode - objek Metode. Dengan mengoptimalkan kompiler JIT, doa reflektif benar-benar tidak membawa penalti kinerja yang besar lagi. Dan selain di samping, katakanlah, menyalin file dari satu lokasi ke lokasi lain, biaya pemanggilan metode yang direfleksikan menjadi tidak berarti.

Ketika saya memikirkannya lebih lanjut, saya menyadari bahwa panggilan balik dalam paradigma OOP membutuhkan pengikatan suatu objek dan metode secara bersamaan - masukkan objek Callback.

Lihat solusi berbasis refleksi saya untuk Callback di Jawa . Gratis untuk penggunaan apa pun.


4

ya, utas ini sudah cukup tua, jadi sangat mungkin jawaban saya tidak membantu untuk pertanyaan itu. Tetapi karena utas ini membantu saya menemukan solusi saya, saya akan tetap melakukannya di sini.

Saya perlu menggunakan metode statis variabel dengan input yang dikenal dan output yang dikenal (keduanya ganda ). Jadi, mengetahui paket metode dan nama, saya bisa bekerja sebagai berikut:

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

untuk fungsi yang menerima satu rangkap sebagai parameter.

Jadi, dalam situasi konkret saya, saya memulainya dengan

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

dan menggunakannya kemudian dalam situasi yang lebih kompleks dengan

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

di mana aktivitas adalah nilai ganda yang berubah-ubah. Saya berpikir mungkin melakukan ini sedikit lebih abstrak dan menggeneralisasikannya, seperti yang dilakukan SoftwareMonkey, tetapi saat ini saya cukup senang dengan cara itu. Tiga baris kode, tidak perlu kelas dan antarmuka, itu tidak terlalu buruk.


terima kasih Rob untuk menambahkan codepenurunan harga, aku terlalu tidak sabar dan bodoh untuk menemukannya ;-)
yogibimbi

4

Untuk melakukan hal yang sama tanpa antarmuka untuk berbagai fungsi:

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
Mengapa? Ini lebih rumit daripada solusi yang diusulkan sebelumnya, yang hanya membutuhkan satu definisi fungsi anonim per alternatif. Per alternatif, Anda membuat kelas dan definisi fungsi anonim. Lebih buruk lagi, ini dilakukan di dua lokasi berbeda dalam kode. Anda mungkin ingin memberikan beberapa pembenaran untuk menggunakan pendekatan ini.
ToolmakerSteve


3

Wow, mengapa tidak hanya membuat kelas Delegasi yang tidak terlalu sulit mengingat bahwa saya sudah melakukannya untuk java dan menggunakannya untuk mengirimkan parameter di mana T adalah tipe pengembalian. Saya minta maaf tetapi sebagai programmer C ++ / C # secara umum baru belajar java, saya perlu pointer fungsi karena mereka sangat berguna. Jika Anda terbiasa dengan kelas apa pun yang berkaitan dengan Informasi Metode, Anda dapat melakukannya. Di perpustakaan java itu adalah java.lang.reflect.method.

Jika Anda selalu menggunakan antarmuka, Anda harus selalu mengimplementasikannya. Dalam penanganan acara, sebenarnya tidak ada cara yang lebih baik untuk mendaftar / membatalkan pendaftaran dari daftar penangan, tetapi untuk delegasi di mana Anda harus menyampaikan fungsi dan bukan tipe nilai, membuat kelas delegasi untuk menanganinya untuk outclass antarmuka.


Bukan jawaban yang berguna, kecuali Anda menunjukkan detail kode. Bagaimana cara membuat kelas Delegasi membantu? Kode apa yang diperlukan per alternatif?
ToolmakerSteve

2

Tidak ada jawaban Java 8 yang memberikan contoh lengkap, kohesif, jadi ini dia.

Deklarasikan metode yang menerima "function pointer" sebagai berikut:

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

Sebut dengan menyediakan fungsi dengan ekspresi lambda:

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


1

Sejak Java8, Anda dapat menggunakan lambdas, yang juga memiliki perpustakaan di API SE 8 resmi.

Penggunaan: Anda perlu menggunakan antarmuka dengan hanya satu metode abstrak. Buat instance darinya (Anda mungkin ingin menggunakan java SE 8 yang sudah disediakan) seperti ini:

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

Untuk informasi lebih lanjut, periksa dokumentasi: https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html


1

Sebelum Java 8, pengganti terdekat untuk fungsi-fungsi seperti pointer adalah kelas anonim. Sebagai contoh:

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

Tetapi sekarang di Java 8 kami memiliki alternatif yang sangat rapi yang dikenal sebagai ekspresi lambda , yang dapat digunakan sebagai:

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

di mana isBiggerThan adalah metode di CustomClass. Kami juga dapat menggunakan referensi metode di sini:

list.sort(MyClass::isBiggerThan);
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.