Cara meniadakan predikat referensi metode

331

Di Java 8, Anda bisa menggunakan referensi metode untuk memfilter aliran, misalnya:

Stream<String> s = ...;
long emptyStrings = s.filter(String::isEmpty).count();

Apakah ada cara untuk membuat referensi metode yang merupakan negasi dari yang sudah ada, yaitu sesuatu seperti:

long nonEmptyStrings = s.filter(not(String::isEmpty)).count();

Saya dapat membuat notmetode seperti di bawah ini tetapi saya bertanya-tanya apakah JDK menawarkan sesuatu yang serupa.

static <T> Predicate<T> not(Predicate<T> p) { return o -> !p.test(o); }
assylias
sumber
6
JDK-8050818 mencakup penambahan Predicate.not(Predicate)metode statis . Tapi masalah itu masih terbuka sehingga kita akan melihat ini paling awal di Java 12 (jika pernah).
Stefan Zobel
1
Sepertinya jawaban ini bisa menjadi solusi pamungkas yang diadaptasi di JDK / 11 juga.
Naman
2
Saya benar-benar ingin melihat sintaks metode referensi khusus untuk kasus ini: s.filter (String ::! IsEmpty)
Mike Twain

Jawaban:

178

Predicate.not( … )

menawarkan metode baru. Predikat # tidak

Jadi Anda dapat meniadakan referensi metode:

Stream<String> s = ...;
long nonEmptyStrings = s.filter(Predicate.not(String::isEmpty)).count();
Anton Balaniuc
sumber
214

Saya berencana mengimpor statis berikut ini untuk memungkinkan referensi metode yang akan digunakan inline:

public static <T> Predicate<T> not(Predicate<T> t) {
    return t.negate();
}

misalnya

Stream<String> s = ...;
long nonEmptyStrings = s.filter(not(String::isEmpty)).count();

Pembaruan : Mulai dari Java-11, JDK juga menawarkan solusi serupa .

davidillsley
sumber
9
@SaintHill tetapi kemudian Anda harus menuliskannya, memberikan parameter nama
flup
5
Tautan Guava yang diperbarui: static.javadoc.io/com.google.guava/guava/23.0/com/google/common/…
Henrik Aasted Sørensen
150

Ada cara untuk membuat referensi metode yang merupakan kebalikan dari referensi metode saat ini. Lihat jawaban @ vlasec di bawah ini yang menunjukkan bagaimana dengan secara eksplisit melemparkan referensi metode ke a Predicatedan kemudian mengubahnya menggunakan negatefungsi. Itu adalah salah satu cara di antara beberapa cara lain yang tidak terlalu merepotkan untuk melakukannya.

Kebalikan dari ini:

Stream<String> s = ...;
int emptyStrings = s.filter(String::isEmpty).count();

Apakah ini:

Stream<String> s = ...;
int notEmptyStrings = s.filter(((Predicate<String>) String::isEmpty).negate()).count()

atau ini:

Stream<String> s = ...;
int notEmptyStrings = s.filter( it -> !it.isEmpty() ).count();

Secara pribadi, saya lebih suka teknik nanti karena saya merasa lebih jelas untuk membaca it -> !it.isEmpty() daripada pemeran verbal panjang dan kemudian meniadakan.

Anda juga bisa membuat predikat dan menggunakannya kembali:

Predicate<String> notEmpty = (String it) -> !it.isEmpty();

Stream<String> s = ...;
int notEmptyStrings = s.filter(notEmpty).count();

Atau, jika memiliki koleksi atau array, cukup gunakan for-loop yang sederhana, memiliki lebih sedikit overhead, dan * mungkin ** lebih cepat:

int notEmpty = 0;
for(String s : list) if(!s.isEmpty()) notEmpty++;

* Jika Anda ingin tahu apa yang lebih cepat, maka gunakan JMH http://openjdk.java.net/projects/code-tools/jmh , dan hindari kode benchmark tangan kecuali menghindari semua optimasi JVM - lihat Java 8: kinerja Streams vs Koleksi

** Saya mendapatkan kritik karena menyarankan bahwa teknik for-loop lebih cepat. Ini menghilangkan pembuatan aliran, menghilangkan menggunakan pemanggilan metode lain (fungsi negatif untuk predikat), dan menghilangkan daftar / penghitung akumulator sementara. Jadi beberapa hal yang disimpan oleh konstruk terakhir yang mungkin membuatnya lebih cepat.

Saya pikir itu lebih sederhana dan lebih baik, meskipun tidak lebih cepat. Jika pekerjaan itu membutuhkan palu dan paku, jangan bawa gergaji dan lem! Saya tahu beberapa dari Anda mempermasalahkan hal itu.

wish-list: Saya ingin melihat Streamfungsi Java berkembang sedikit sekarang karena pengguna Java lebih mengenalnya. Misalnya, metode 'hitung' di Stream dapat menerima Predicatesehingga ini dapat dilakukan secara langsung seperti ini:

Stream<String> s = ...;
int notEmptyStrings = s.count(it -> !it.isEmpty());

or

List<String> list = ...;
int notEmptyStrings = lists.count(it -> !it.isEmpty());
Koordinator
sumber
Mengapa Anda mengatakan itu jauh lebih cepat ?
José Andias
@ JoséAndias (1) Apakah lebih cepat atau 'jauh lebih cepat'? (2) Jika demikian, mengapa? Apa yang telah Anda tentukan?
Koordinator
3
Saya meminta Anda untuk menjelaskan "jauh lebih cepat untuk dijalankan". Pertanyaannya: (1) Apakah lebih cepat atau 'jauh lebih cepat'? (2) Jika demikian, mengapa? Apa yang telah Anda tentukan? lebih baik dijawab oleh Anda, penulis pernyataan itu. Saya tidak menganggapnya lebih cepat atau lebih lambat. Terima kasih
José Andias
2
Lalu saya akan membuang ini untuk pertimbangan Anda - Ini menghilangkan pembuatan aliran, menghilangkan menggunakan pemanggilan metode lain (fungsi negatif untuk predikat), dan menghilangkan daftar / penghitung akumulator sementara. Jadi beberapa hal yang disimpan oleh konstruk terakhir. Saya tidak yakin apakah itu lebih cepat atau seberapa cepat, tetapi saya menganggap itu 'jauh' lebih cepat. Tapi mungkin 'banyak' subjektif. Lebih mudah untuk mengkode lebih lambat daripada membuat predikat negatif dan streaming untuk melakukan penghitungan langsung. Preferensi saya
Koordinator
4
negate () sepertinya solusi yang ideal. Sayang sekali itu tidak statis seperti Predicate.negate(String::isEmpty);tanpa pengecoran rumit.
Joel Shemtov
92

Predicatememiliki metode and, ordan negate.

Namun, String::isEmptybukan Predicate, itu hanya String -> Booleanlambda dan itu masih bisa menjadi apa saja, misalnya Function<String, Boolean>. Jenis inferensi inilah yang perlu terjadi terlebih dahulu. The filterMetode menyimpulkan ketik secara implisit . Tetapi jika Anda meniadakannya sebelum meneruskannya sebagai argumen, itu tidak lagi terjadi. Seperti @axtavt sebutkan, inferensi eksplisit dapat digunakan sebagai cara yang jelek:

s.filter(((Predicate<String>) String::isEmpty).negate()).count()

Ada cara lain yang disarankan dalam jawaban lain, dengan notmetode statis dan lambda kemungkinan besar menjadi ide terbaik. Ini menyimpulkan bagian dr .


Namun, jika Anda ingin lebih memahami inferensi tipe lambda, saya ingin menjelaskannya sedikit lebih mendalam, menggunakan contoh. Lihat ini dan coba cari tahu apa yang terjadi:

Object obj1                  = String::isEmpty;
Predicate<String> p1         = s -> s.isEmpty();
Function<String, Boolean> f1 = String::isEmpty;
Object obj2                  = p1;
Function<String, Boolean> f2 = (Function<String, Boolean>) obj2;
Function<String, Boolean> f3 = p1::test;
Predicate<Integer> p2        = s -> s.isEmpty();
Predicate<Integer> p3        = String::isEmpty;
  • obj1 tidak dapat dikompilasi - lambdas perlu menyimpulkan antarmuka fungsional (= dengan satu metode abstrak)
  • p1 dan f1 berfungsi dengan baik, masing-masing menyimpulkan tipe yang berbeda
  • obj2 gips a Predicate keObject - konyol tapi valid
  • f2 gagal saat runtime - Anda tidak dapat melakukan cast Predicate keFunction , itu tidak lagi tentang inferensi
  • f3 berfungsi - Anda memanggil metode predikat test yang ditentukan oleh lambda-nya
  • p2 tidak mengkompilasi - Integer tidak punyaisEmpty metode
  • p3 tidak dapat dikompilasi - tidak ada String::isEmptymetode statis denganInteger argumen

Saya harap ini membantu mendapatkan lebih banyak wawasan tentang cara kerja kesimpulan tipe.

Vlasec
sumber
46

Membangun jawaban dan pengalaman pribadi orang lain:

Predicate<String> blank = String::isEmpty;
content.stream()
       .filter(blank.negate())
Jose Alban
sumber
4
Menarik - Anda tidak bisa inline ::referensi fungsional seperti yang mungkin diinginkan ( String::isEmpty.negate()), tetapi jika Anda menetapkan variabel terlebih dahulu (atau dilemparkan ke yang Predicate<String>pertama), itu berfungsi. Saya pikir lambda !akan lebih mudah dibaca dalam banyak kasus, tetapi sangat membantu untuk mengetahui apa yang bisa dan tidak bisa dikompilasi.
Joshua Goldberg
2
@ JoshuaGoldberg saya menjelaskan bahwa dalam jawaban saya: Referensi metode bukanlah Predikat sendiri. Di sini, casting dilakukan oleh variabel.
Vlasec
17

Pilihan lain adalah memanfaatkan lambda casting dalam konteks yang tidak ambigu menjadi satu kelas:

public static class Lambdas {
    public static <T> Predicate<T> as(Predicate<T> predicate){
        return predicate;
    }

    public static <T> Consumer<T> as(Consumer<T> consumer){
        return consumer;
    }

    public static <T> Supplier<T> as(Supplier<T> supplier){
        return supplier;
    }

    public static <T, R> Function<T, R> as(Function<T, R> function){
        return function;
    }

}

... dan kemudian impor statis kelas utilitas:

stream.filter(as(String::isEmpty).negate())
Askar Kalykov
sumber
1
Saya benar-benar terkejut ini berhasil - tetapi tampaknya JDK lebih menyukai Predikat <T> daripada Function <T, Boolean>. Tetapi Anda tidak akan mendapatkan Lambdas untuk memberikan apa pun ke Function <T, Boolean>.
Vlasec
Ini berfungsi untuk String tetapi tidak untuk List: Error: (20, 39) java: referensi as is ambiguous kedua metode <T> as (java.util.function.Consumer <T>) di com.strands.sbs.function. Lambdas dan metode <T, R> as (java.util.function.Function <T, R>) di com.strands.sbs.function.Lambdas match
Daniel Pinyol
Daniel, itu mungkin terjadi jika Anda mencoba menggunakan metode kelebihan beban :)
Askar Kalykov
Sekarang saya mengerti tipe inference jauh lebih baik dari aslinya, saya mengerti cara kerjanya. Pada dasarnya, ia hanya menemukan satu-satunya opsi yang berfungsi. Terlihat menarik, saya hanya tidak tahu apakah ada beberapa nama yang lebih baik yang tidak menyebabkan boilerplate.
Vlasec
12

Bukankah Predicate#negateseharusnya apa yang Anda cari?

Marco13
sumber
Anda harus mendapatkan yang Predicatepertama.
Sotirios Delimanolis
21
Anda harus cor String::isEmpty()untuk Predicate<String>sebelum - itu sangat jelek.
axtavt
3
@assylias Gunakan sebagai Predicate<String> p = (Predicate<String>) String::isEmpty;dan p.negate().
Sotirios Delimanolis
8
@ SotiriosDelimanolis Saya tahu, tapi itu mengalahkan tujuannya - Saya lebih suka menulis s -> !s.isEmpty()dalam kasus itu!
assylias
@assylias: Ya, saya percaya itu sebenarnya idenya; bahwa hanya menulis lambda tangan adalah fallback yang dimaksud.
Louis Wasserman
8

Dalam hal ini Anda dapat menggunakan org.apache.commons.lang3.StringUtilsdan melakukannya

int nonEmptyStrings = s.filter(StringUtils::isNotEmpty).count();
di luar batas
sumber
6
Tidak. Pertanyaannya adalah bagaimana meniadakan referensi metode apa pun, dan mengambil String::isEmptysebagai contoh. Ini masih merupakan informasi yang relevan jika Anda memiliki kasus penggunaan ini, tetapi jika hanya menjawab kasus penggunaan String, maka itu tidak boleh diterima.
Anthony Drogon
4

Saya telah menulis kelas utilitas lengkap (terinspirasi oleh proposal Askar) yang dapat mengambil ekspresi Java 8 lambda dan mengubahnya (jika berlaku) menjadi standar apa pun yang diketikkan Java 8 lambda yang ditentukan dalam paket java.util.function. Misalnya Anda dapat melakukan:

  • asPredicate(String::isEmpty).negate()
  • asBiPredicate(String::equals).negate()

Karena akan ada banyak ambiguitas jika semua metode statis akan dinamai adil as() , saya memilih untuk memanggil metode "sebagai" diikuti oleh jenis yang dikembalikan. Ini memberi kita kendali penuh atas interpretasi lambda. Di bawah ini adalah bagian pertama dari kelas utilitas (agak besar) mengungkapkan pola yang digunakan.

Lihatlah kelas lengkapnya di sini (di intisari).

public class FunctionCastUtil {

    public static <T, U> BiConsumer<T, U> asBiConsumer(BiConsumer<T, U> biConsumer) {
        return biConsumer;
    }

    public static <T, U, R> BiFunction<T, U, R> asBiFunction(BiFunction<T, U, R> biFunction) {
        return biFunction;
    }

     public static <T> BinaryOperator<T> asBinaryOperator(BinaryOperator<T> binaryOperator) {
        return binaryOperator;
    }

    ... and so on...
}
Per-Åke Minborg
sumber
4

Anda dapat menggunakan Predikat dari Eclipse Collections

MutableList<String> strings = Lists.mutable.empty();
int nonEmptyStrings = strings.count(Predicates.not(String::isEmpty));

Jika Anda tidak dapat mengubah string dari List:

List<String> strings = new ArrayList<>();
int nonEmptyStrings = ListAdapter.adapt(strings).count(Predicates.not(String::isEmpty));

Jika Anda hanya membutuhkan negasi, String.isEmpty()Anda juga dapat menggunakannya StringPredicates.notEmpty().

Catatan: Saya adalah kontributor untuk Eclipse Collections.

Nikhil Nanivadekar
sumber
1

Anda bisa melakukannya selama ini emptyStrings = s.filter(s->!s.isEmpty()).count();

Narsi Reddy Nallamilli
sumber
0

Jika Anda menggunakan Spring Boot (2.0.0+) Anda dapat menggunakan:

import org.springframework.util.StringUtils;

...
.filter(StringUtils::hasLength)
...

Yang tidak: return (str != null && !str.isEmpty());

Jadi itu akan memiliki efek negasi yang diperlukan untuk isEmpty

Gilad Peleg
sumber