Java'daki bir işlev göstergesinin en yakın yerine yerleştirme nedir?

Yaklaşık on satır kod içeren bir yöntemim var. Bir kod satırını değiştirecek küçük bir hesaplama dışında, aynı şeyi yapacak daha fazla yöntem oluşturmak istiyorum. Bu, tek bir satırı değiştirmek için bir işlev işaretçisini geçirmek için ideal bir uygulamadır, ancak Java'da işlev işaretçileri yoktur. En iyi alternatifim nedir?

292
23 сент. 23 Eylül tarihinde Kertenkele Bill tarafından ayarlanır 2008-09-23 20:20 08: 20 : 20'de 2008-09-23 20:20
ответ 21 cevaplar

Anonim iç sınıf

Bir int döndüren bir String parametresiyle bir işlev iletmek istediğinizi varsayalım.
Öncelikle mevcut olanı tekrar kullanamıyorsanız, işlevli arayüzü tek üye olarak tanımlamanız gerekir.

 interface StringFunction { int func(String param); } 

Bir işaretçiyi kabul eden yöntem şöyle bir StringFunction örneğini kabul edecektir:

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

Ve buna şöyle denir:

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

EDIT: Java 8'de bir lambda ifadesi kullanarak çağırabilirsiniz:

 ref.takingMethod(param -> bodyExpression); 
267
23 сент. Cevap 23 Eylül sblundy verilir . 2008-09-23 20:21 '08, 20:21, 2008-09-23 20:21

Her "işlev işaretçisi" için hesaplamaları yapacak küçük bir functor sınıfı oluşturuyorum. Tüm sınıfların uygulayacağı bir arayüz tanımlayın ve bu nesnelerin örneklerini büyük işlevinize geçirin. Bu, " takım şablonu " ve " strateji şablonu " nun bir kombinasyonudur.

border=0

@Sblundy örneği iyidir.

32
23 сент. Tarafından cevap Blair Conrad 23 Eyl 2008-09-23 20:27 '08, 08:27, 2008-09-23 20:27

Bir satırda yapabileceğiniz önceden belirlenmiş sayıda farklı hesaplama olduğunda, bir numaralandırma kullanmak, bir strateji modelini uygulamak için hızlı ancak açık bir yoldur.

 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çıkçası, strateji yönteminin beyanı ve her uygulamanın tam olarak bir kopyası tek bir sınıf / dosyada tanımlanmıştır.

27
27 марта '09 в 3:16 2009-03-27 03:16 Cevap javashlook tarafından 27 Mart 09: 3: 16'da verildi 2009-03-27 03:16

İletmek istediğiniz işlevleri sağlayan bir arayüz oluşturmanız gerekir. örneğin:

  public interface Function1<S, T> {  public S eval(T a); } 

Ardından, bir işlevi iletmeniz gerektiğinde, bu arabirimi uygulayabilirsiniz:

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

Son olarak, harita işlevi, İşlev1'e iletilen işlevi kullanır:

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

Parametreleri geçirmeniz gerekmiyorsa, sık sık kendi arabiriminiz yerine Runnable'ı kullanabilirsiniz veya parametre sayısını daha az “sabit” yapmak için çeşitli diğer yöntemleri kullanabilirsiniz, ancak bu genellikle güvenlik türüyle bir takastır (Veya yapıcıyı geçersiz kılabilirsiniz). fonksiyon nesnenizin bu şekilde parametrelerde yer alması için birçok yaklaşım vardır ve bazı durumlarda bazı durumlarda daha iyi çalışır.)

24
23 сент. Cevap rcreswick tarafından verilir 23 Eylül 2008-09-23 20:30 08: 08:30 - 2008-09-23 20:30

Bunu da yapabilirsiniz (bazı durumlarda RARE anlamlı olur). Sorun (ve bu büyük bir sorundur) sınıfı / arayüzü kullanarak tüm güvenlik türlerini kaybetmeniz ve yöntemin olmadığı durumlarla ilgilenmeniz gerekir.

Bu, erişim kısıtlamalarını göz ardı edebileceğiniz ve özel yöntemler çağırabileceğiniz (örnekte gösterilmemiştir, ancak derleyicinin aramanıza izin vermediği yöntemleri çağırabilirsiniz) "avantajına" sahiptir.

Yine, bu mantıklı geldiğinde nadir bir durumdur, ancak bu durumlarda iyi bir araçtır.

 import java.> 
15
27 марта '09 в 0:39 2009-03-27 00:39 Cevap TofuBeer 27 Mart 099 'da 0:39 2009-03-27 00:39

İşlecini kullanan yöntemlere referanslar ::

Yöntem referanslarını, yöntemin işlevsel bir arabirimi kabul ettiği yöntem argümanlarında kullanabilirsiniz. İşlevsel bir arabirim, yalnızca bir soyut yöntem içeren herhangi bir arabirimdir. (İşlevsel bir arabirim bir veya daha fazla varsayılan yöntem veya statik yöntem içerebilir.)

IntBinaryOperator işlevsel bir arayüzdür. Soyut yöntemi, applyAsInt , parametreleri olarak iki int alır ve bir int döndürür. Math.max ayrıca iki int alır ve bir int döndürür. Bu örnekte, A.method(Math::max); parameter.applyAsInt değerini iki giriş değerini Math.max değerine Math.max ve bu Math.max sonucunu 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.> 

Genel olarak şunları kullanabilirsiniz:

 method1(Class1::method2); 

yerine:

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

ne için kısaltılır:

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

Daha fazla bilgi için, Java 8 ve Java § 15.13 dil spesifikasyonundaki :: (çift kolon) operatörüne bakın .

14
23 февр. Cevap, 23 Şubat'ta Şapkalı Adam tarafından verildi. 2014-02-23 23:37 '14, saat 11:37, 2014-02-23 23:37

Yalnızca farklı bir satırınız varsa, flag ve bir satırı veya diğerini çağıran bir if (flag) ifadesi gibi bir parametre ekleyebilirsiniz.

13
27 марта '09 в 0:56 2009-03-27 00:56 Cevap Peter Lawrey tarafından 27.09.09'da 0:56 2009-03-27 00:56 tarihinde verilmiştir.
12
24 сент. Cevap 24 Eylül'de Dave L. tarafından verildi . 2008-09-24 00:22 '08, 0:22 2008-09-24 00:22

Yeni fonksiyonel arayüzler ve işleç :: kullanarak Java 8'e referanslar.

Java 8, "@Functional Interface" işaretçilerle yöntem referanslarını (MyClass :: new) destekleyebilir. Aynı yöntem adına gerek yok, sadece aynı yöntem imzası gerekli.

örnek:

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

Peki, burada ne var?

  • İşlevsel bir arabirim, yalnızca bir yöntem bildirimi içeren @FunctionalInterface ile ilişkilendirilmiş veya açıklanmamış bir arabirimdir.
  • Yöntemlere yapılan başvurular sadece özel bir sözdizimidir, şuna benzer: objectInstance :: methodName, her şeyden başka bir şey değil.
  • Kullanım durumu, yalnızca bir atama ifadesidir ve ardından bir arabirim yöntemi çağrısıdır.

SADECE VE SADECE BU İÇİN SEVERLER LİSTESİ İÇİN FONKSİYONEL ARAYÜZLER KULLANMALIDIR!

Zira diğer bütün fonksiyon göstergeleri kodu okumak için ve anlama yeteneği için gerçekten kötüdür. Bununla birlikte, yöntemlere doğrudan referanslar, örneğin foreach için bazen uygundur.

Önceden tanımlanmış birkaç fonksiyonel arayüz vardır:

 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'nın önceki sürümleri için, yukarıda Adrian Petrescu tarafından belirtildiği gibi, benzer işlevselliğe ve sözdizimine sahip Guava kütüphanelerini denemelisiniz.

Daha fazla araştırma için bkz. Java 8 Cheatsheet

ve Java Dil Belirtimi §15.13 bağlantısı için Şapkalı Adam'a teşekkürler .

9
19 авг. Tarafından verilen cevap user3002379 19 Ağu 2014-08-19 13:43 '14, 13:43 2014-08-19 13:43

@Sblundy cevabı harika, ancak anonim iç sınıfların iki küçük kusuru var; bunlardan bazıları, kural olarak tekrar kullanılamayacakları ve ikincil olanı ise hantal bir sözdizimi ile.

Ana sınıfta (hesaplamaları yapan) herhangi bir değişiklik yapmadan şablonunun tam sınıflara genişlemesi iyidir.

Yeni bir sınıf örneği oluşturduğunuzda, denkleminizde sabit olarak hareket edebilen parametreleri bu sınıfa geçirebilirsiniz, böylece iç sınıflarınızdan biri şöyle görünürse:

 f(x,y)=x*y 

ama bazen buna ihtiyacın var:

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

ve muhtemelen üçüncü:

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

iki anonim iç sınıf oluşturmak veya doğrudan geçiş parametresini eklemek yerine, oluşturduğunuz bir ACTUAL sınıfı oluşturabilirsiniz:

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

Sadece sınıfta sabiti koruyacak ve arayüzde belirtilen yöntemde kullanacaktır.

Aslında, işlevinizin kaydedilmeyeceğini / yeniden kullanılmayacağını bildiğiniz takdirde, bunu yapabilirsiniz:

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

Ancak değişmez sınıflar daha güvenlidir - bunun gibi bir sınıfı yapmak için bir bahane düşünemiyorum.

Gerçekten sadece yayınladım çünkü ne zaman isimsiz bir iç sınıf duyduğumda sıkıyordum - “Zorunlu” olan bir çok gereksiz kod gördüm çünkü programcı ilk ve asıl kullanmak zorunda kaldığında yaptığı isimsizdi. kararını yeniden düşünmedi.

9
04 мая '10 в 21:44 2010-05-04 21:44 Cevap Bill K tarafından 04 '10 'da 21:44 2010-05-04 21:44 tarihinde verilmiştir.

Çok popüler hale gelen Google Guava kütüphaneleri ortak bir İşleve sahiptir ve API'lerinin birçok yerinde çalıştıklarını tahmin eder.

6
17 дек. Cevap 17 Ocak tarihinde Adrian Petrescu tarafından verildi. 2010-12-17 00:32 '10, 0:32 2010-12-17 00:32

Aynı şeyi, bir dizi işlev için arabirimsiz yapmak için:

 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) { ag(x); } }, new NameFuncPair("B") { @Override void f(String x) { bh(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); } } 
5
18 марта '11 в 5:53 2011-03-18 05:53 Cevap vwvan 18 / 11'de 5: 53'te 2011-03-18 05:53

Tamam, bu şube zaten oldukça eski, bu yüzden cevabımın soruya yardımcı olmayacağı muhtemel. Ancak bu konu benim çözümümü bulmama yardımcı olduğu için, yine de göndereceğim.

Bilinen bir girdi ve bilinen bir çıktıya sahip değişken statik bir yöntem kullanmam gerekiyordu (ikisi de çift ). Böylece, paketi ve yöntemin adını bilerek, aşağıdaki gibi çalışabilirim:

 java.> 

bir çift parametre alan bir fonksiyon için.

Böylece, benim özel durumumda,

 java.> 

ve daha sonra daha zor durumda denir

 return (java.> 

faaliyetin keyfi bir çifte değer olduğu. Sanırım biraz daha soyut yapmak ve özetlemek, SoftwareMonkey'in yaptığı gibi, ama şu anda nasıl olduğu konusunda oldukça mutluyum. Sınıflar ve arayüzler olmadan üç satır kod, bu o kadar da kötü değil.

4
09 нояб. Cevap verildi yogibimbi 09 kasım. 2011-11-09 17:59 '11 17: 59'da 2011-11-09 17:59

Bana bir strateji şablonu gibi geliyor. Java şablonlarını fluffycat.com adresinde kontrol edin.

4
25 сент. Cevap Dennis S 25 Eylül. 2008-09-25 06:21 '08, 06:21, 2008-09-25 06:21

Java programlaması bir geri çağırma işlevi olduğunda gerçekten özlediğim şeylerden biri. Onlara duyulan ihtiyacın sürdüğü durumlardan biri, her eleman için bazı özel eylemler yapmak istediğiniz hiyerarşinin özyinelemeli işlemesiydi. Bir dizin ağacı veya veri yapısı gibi. İçimdeki minimalizm, arayüzü ve daha sonra her bir özel durum için uygulamayı tanımlamaya izin vermiyor.

Bir keresinde neden olmasın diye merak ettim. Yöntemlere işaretçilerimiz var - Yöntem nesnesi. En iyi duruma getirilmiş JIT derleyicileri ile, dönüşlü bir arama gerçekten büyük bir performans cezası oluşturmaz. Ayrıca, bir dosyayı bir yerden bir yere kopyalamanın yanı sıra, yansıtılan yöntemi çağırmanın maliyeti de azalır.

Bunun hakkında daha fazla düşündüğüm gibi, OOP paradigmasındaki geri çağrının, nesneyi ve yöntemi birbirine bağlamayı gerektirdiğini anladım - geri çağırma nesnesine girin.

Java'daki Geri Çağrılar için yansıma tabanlı çözümüme bakın . Herhangi bir kullanım için ücretsiz.

4
28 янв. Cevap Lawrence Dol tarafından verildi Jan 28 2010-01-28 06:20 '10, 06:20 2010-01-28 06:20

Vay canına, neden java için zaten yaptım ve T'ye geri dönüş türü olan parametreye geçmek için kullandıysam, o kadar güçlü olmayan bir temsilci sınıfı oluşturmuyordum. Üzgünüz, fakat genel olarak bir C ++ / C # programcısı olarak, sadece java öğrenerek, fonksiyon göstergelerine ihtiyacım var çünkü çok uygunlar. Bir yöntem hakkında bilgi veren herhangi bir sınıfa aşina iseniz, bunu yapabilirsiniz. Java kütüphanelerinde, java.>

Her zaman bir arayüz kullanıyorsanız, her zaman uygulamanız gerekir. Verileri işlerken, işleyiciler listesinden kaydolmak / kaydını silmek için daha iyi bir yol yoktur, ancak delegeler için, işlevleri iletmeniz gereken temsilciler için, temsilci sınıfını arabirimin sınıfları için işlenmesini sağlayan değer türü değil.

3
02 марта '11 в 7:11 2011-03-02 07:11 Cevap, Robert tarafından 02 '11'de 07:11 2011-03-02 07:11 tarihinde verilmiştir.

Lambdaj'a göz atın

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

ve özellikle yeni kapanış fonksiyonu

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

ve anlamsız bir arayüz oluşturmadan ya da çirkin iç sınıflar kullanmadan bir kapama ya da işlev göstergesini tanımlamanın çok okunaklı bir yolunu bulacaksınız.

3
12 сент. Cevap Mario Fusco 12 Eylül tarafından verildi . 2009-09-12 10:39 '09, 10:39 2009-09-12 10:39

Java 8'in ortaya çıkmasından önce, işlev dizini işlevlerinin en yakın yerine yerleştirilen isim anonim sınıftır. Örneğin:

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

Fakat şimdi Java 8'de lambda ifadesi olarak bilinen ve şu şekilde kullanılabilen çok temiz bir alternatifimiz var:

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

isBiggerThan, CustomClass bir yöntemdir. Ayrıca burada yöntemlere referanslar kullanabiliriz:

 list.sort(MyClass::isBiggerThan); 
1
23 июня '15 в 14:06 2015-06-23 14:06 Cevap i_am_zero 23 Haziran ' 15'te 14:06 2015-06-23 14:06 tarihinde verilmiştir.

Birisi davranışını belirlemek için bir parametre setini ve yürütülecek başka bir parametre setini alan bir işlevi geçmeye çalışırsa, örneğin Şema:

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

Java'da parametreli davranışı olan transfer fonksiyonunu görün

1
11 июня '14 в 2:02 2014-06-11 02:02 Cevap Scott Emmons tarafından 11 Haziran 14'te 2:02 2014-06-11 02:02 tarihinde verilmiştir.

Java8 ile başlayarak, SE 8 API'nin resmi arayüzünde kütüphaneleri bulunan lambdaları da kullanabilirsiniz.

Uygulama: Arabirimi yalnızca bir soyut yöntemle kullanmanız gerekir. Örnek verin (önceden verilen Java kodunu 8 kullanabilirsiniz) aşağıdaki gibi:

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

Daha fazla bilgi için belgelere bakın: https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html

1
10 мая '15 в 15:50 2015-05-10 15:50 Cevap 10 Mayıs 15 tarihinde Alex tarafından 15: 50'de verildi 2015-05-10 15:50

Java 8'deki cevapların hiçbiri tam ve tutarlı bir örnek vermedi, işte burada.

Bir "işlev işaretçisi" kabul eden bir yöntemi aşağıdaki şekilde bildirin:

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

Fonksiyonu lambda ifadesi vererek şöyle adlandırın:

 doCalculation((i) -> i.toString(), 2); 
0
07 окт. Cevap mrts 7 Ekim tarafından verildi 2016-10-07 20:08 '16, 20:08 2016-10-07 20:08

etiketleriyle ilgili diğer sorular veya Soru Sor