Java8'de void (Void değil) yöntemleri için işlev türleri nasıl belirtilir?


143

Birinci sınıf vatandaş olarak nasıl işlev gördüğünü öğrenmek için Java 8 ile oynuyorum. Şu snippet'e sahibim:

package test;

import java.util.*;
import java.util.function.*;

public class Test {

    public static void myForEach(List<Integer> list, Function<Integer, Void> myFunction) {
      list.forEach(functionToBlock(myFunction));
    }

    public static void displayInt(Integer i) {
      System.out.println(i);
    }


    public static void main(String[] args) {
      List<Integer> theList = new ArrayList<>();
      theList.add(1);
      theList.add(2);
      theList.add(3);
      theList.add(4);
      theList.add(5);
      theList.add(6);
      myForEach(theList, Test::displayInt);
    }
}

Ne yapmaya çalışıyorum bir yöntem başvurusu kullanarak yöntem displayIntyöntemi geçmek myForEach. Derleyici aşağıdaki hatayı üretir:

src/test/Test.java:9: error: cannot find symbol
      list.forEach(functionToBlock(myFunction));
                   ^
  symbol:   method functionToBlock(Function<Integer,Void>)
  location: class Test
src/test/Test.java:25: error: method myForEach in class Test cannot be applied to given ty
pes;
      myForEach(theList, Test::displayInt);
      ^
  required: List<Integer>,Function<Integer,Void>
  found: List<Integer>,Test::displayInt
  reason: argument mismatch; bad return type in method reference
      void cannot be converted to Void

Derleyici bundan şikayet ediyor void cannot be converted to Void. myForEachKod derler böylece imzasında işlev arabiriminin türünü belirtmek nasıl bilmiyorum . Ben sadece dönüş türünü değiştirebilir biliyoruz displayIntiçin Voidve daha sonra dönmek null. Ancak, başka bir yere geçmek istediğim yöntemi değiştirmenin mümkün olmadığı durumlar olabilir. Olduğu displayIntgibi yeniden kullanmanın kolay bir yolu var mı ?

Yanıtlar:


244

Yanlış arabirim türünü kullanmaya çalışıyorsunuz. İşlev türü bu durumda uygun değildir, çünkü bir parametre alır ve bir dönüş değeri vardır. Bunun yerine Tüketici'yi kullanmalısınız (eski adıyla Block)

İşlev türü

interface Function<T,R> {
  R apply(T t);
}

Ancak, Tüketici türü aradığınız ürünle uyumludur:

interface Consumer<T> {
   void accept(T t);
}

Bu nedenle, Tüketici bir T alan ve hiçbir şey döndürmeyen (geçersiz) yöntemlerle uyumludur. Ve istediğin bu.

Örneğin, bir listedeki tüm öğeleri görüntülemek istersem, lambda ifadesiyle bunun için bir tüketici oluşturabilirim:

List<String> allJedi = asList("Luke","Obiwan","Quigon");
allJedi.forEach( jedi -> System.out.println(jedi) );

Yukarıda, bu durumda lambda ifadesinin bir parametre aldığını ve dönüş değeri olmadığını görebilirsiniz.

Şimdi, bu tür bir tüketim oluşturmak için lambda ifadesi yerine bir yöntem başvurusu kullanmak istedim, o zaman bir String alır ve geçersiz, döndüren bir yönteme ihtiyacım var değil mi?

Ben yöntem referansların farklı türlerini kullanmak, ancak bu durumda kullanarak bir nesne yöntemi referans XML'da avantajını izin verebilir printlnyöntemi System.outbu gibi nesne:

Consumer<String> block = System.out::println

Yoksa basitçe yapabilirim

allJedi.forEach(System.out::println);

printlnSadece gibi bir değeri alır ve bir dönüş türü void çünkü yöntem uygundur acceptConsumer'da yöntemle.

Bu nedenle, kodunuzda yöntem imzanızı bir şekilde değiştirmeniz gerekir:

public static void myForEach(List<Integer> list, Consumer<Integer> myBlock) {
   list.forEach(myBlock);
}

Ve sonra sizin durumunuzda statik bir yöntem referansı kullanarak bir tüketici oluşturabilmeniz gerekir:

myForEach(theList, Test::displayInt);

Nihayetinde, myForEachyönteminizden tamamen kurtulabilir ve basitçe şunları yapabilirsiniz:

theList.forEach(Test::displayInt);

Birinci Sınıf Vatandaş Olarak İşlevler Hakkında

Gerçek şu ki, Java 8 birinci sınıf vatandaş olarak işlevlere sahip olmayacak, çünkü dile yapısal bir işlev türü eklenmeyecek. Java, lambda ifadelerinden ve yöntem referanslarından işlevsel arabirimler uygulamaları oluşturmak için basit bir alternatif sunacaktır. Sonuçta lambda ifadeleri ve yöntem referansları nesne referanslarına bağlı olacaktır, bu nedenle sahip olduğumuz tek şey birinci sınıf vatandaş olarak nesnelerdir. Önemli olan işlevselliktir, çünkü nesneleri parametre olarak geçirebilir, değişken referanslara bağlayabilir ve diğer yöntemlerden değerler olarak döndürebiliriz, o zaman hemen hemen benzer bir amaca hizmet ederler.


1
Bu arada, JDK 8 API'nın en son sürümleri Blockiçin değiştirildiConsumer
Edwin Dalorzo

4
Son paragraf bazı önemli olgular üzerine ışıldar: Lambda ifadeleri ve yöntem referansları sözdizimsel bir eklemeden daha fazlasıdır. JVM'nin kendisi, anonim sınıflardan (en önemlisi MethodHandle) yöntem referanslarından daha verimli bir şekilde yöntem referanslarını işlemek için yeni türler sağlar ve bunu kullanarak Java derleyicisi daha verimli kod üretebilir, örn. ek sınıflar.
Feuermurmel

7
Ve doğru sürümü Function<Void, Void>olduğunu Runnable.
OrangeDog

2
@OrangeDog Bu tamamen doğru değil. Yorumlarında Runnableve Callablearayüzleri onlar ile birlikte kullanılır olması gerekiyordu olduğunu yazıyor parçacığı . Bunun can sıkıcı sonucu, run()yöntemi doğrudan çağırırsanız bazı statik analiz araçlarının (Sonar gibi) şikayet edeceğidir .
Denis

Sadece bir Java geçmişinden geliyor, özellikle Java8, Java8'den beri fonksiyonel arayüzler ve lambdasların kullanılmasından sonra bile birinci sınıf vatandaşlar olarak fonksiyonları tedavi edemediği durumlar neler?
Vivek Sethi

21

Bir işlevi argüman almayan ve sonuç vermeyen (geçersiz) argüman olarak kabul etmeniz gerektiğinde, bence böyle bir şeye sahip olmak en iyisidir.

  public interface Thunk { void apply(); }

kodunuzda bir yerde. Fonksiyonel programlama derslerimde bu işlevleri tanımlamak için 'thunk' kelimesi kullanıldı. Neden java.util.function içinde değil benim anlayış ötesinde.

Diğer durumlarda, java.util.function öğesinde istediğim imzayla eşleşen bir şey olsa bile, arabirimin adlandırılması kodumdaki işlevin kullanımıyla eşleşmediğinde hala her zaman doğru hissetmez. Sanırım burada iş parçacığı sınıfıyla ilişkili bir terim olan 'Runnable' ile ilgili başka bir yerde yapılan benzer bir nokta, bu yüzden ihtiyacım olan imzayı olsa da, muhtemelen okuyucuyu karıştırması muhtemel.


Özellikle Runnableişaretli istisnalara izin vermez, bu da birçok uygulama için işe yaramaz hale getirir. Yana Callable<Void>ya yararlı değildir, bunu görünüyor kendi tanımlamanız gerekir. Çirkin.
Jesse Glick

Kontrol edilen istisna sorununa referans olarak, Java'nın yeni fonksiyonları genel olarak bununla mücadele eder. Akış API'sı gibi bazı şeyler için büyük bir engelleyici. Onların tarafında çok kötü tasarım.
user2223059

0

Ayarla dönüş tipi Voidyerine voidvereturn null

// Modify existing method
public static Void displayInt(Integer i) {
    System.out.println(i);
    return null;
}

VEYA

// Or use Lambda
myForEach(theList, i -> {System.out.println(i);return null;});

-2

Bunun yerine Tüketici arayüzünü kullanmanız gerektiğini hissediyorum Function<T, R>.

Bir Tüketici temel olarak bir değeri kabul etmek ve hiçbir şey döndürmemek (yani geçersiz) için tasarlanmış işlevsel bir arabirimdir

Sizin durumunuzda, kodunuzda başka bir yerde şöyle bir tüketici oluşturabilirsiniz:

Consumer<Integer> myFunction = x -> {
    System.out.println("processing value: " + x);    
    .... do some more things with "x" which returns nothing...
}

Ardından myForEachkodunuzu aşağıdaki snippet ile değiştirebilirsiniz :

public static void myForEach(List<Integer> list, Consumer<Integer> myFunction) 
{
  list.forEach(x->myFunction.accept(x));
}

MyFunction işlevini birinci sınıf bir nesne olarak görürsünüz.


2
Bu mevcut cevapların ötesine neler getiriyor?
Matthew
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.