Pertanyaan Mengapa tidak Predicate Java 8 memperpanjang Fungsi


Jika saya menulis antarmuka Predikat, saya ingin menyandikan di antarmuka fakta bahwa itu hanya sebuah fungsi yang mengembalikan boolean primitif, seperti ini:

@FunctionalInterface
public interface Predicate<T> extends Function<T, Boolean> {

    boolean test(T t);

    @Override
    default Boolean apply(T t) {
        return Boolean.valueOf(test(t));
    }
}

Saya bertanya-tanya, apakah ada alasan kuat mengapa desainer API Java 8 memilih untuk menjaga Predikat benar-benar terpisah dari Fungsi? Adakah bukti bahwa mereka mempertimbangkan untuk melakukannya dan memutuskan untuk tidak melakukannya? Saya kira pertanyaan serupa berlaku untuk semua antarmuka fungsional 'khusus' lainnya seperti Konsumen (bisa menjadi Fungsi <T, Void>), Pemasok (Fungsi <Void, T>) dan fungsi primitif seperti IntFungsi (Fungsi <Integer, T>).

Saya belum memikirkan secara mendalam dan mendalam tentang semua konsekuensi ini, jadi saya mungkin kehilangan sesuatu.

EDIT: Beberapa jawaban memperjelas perbedaan semantik antara menerapkan dan menguji. Saya tidak mengatakan saya tidak menghargai perbedaan itu, dan saya setuju bahwa bermanfaat untuk memiliki perbedaan ini. Yang tidak saya pahami adalah mengapa Predikat tidak juga berfungsi dengan cara yang sama seperti misalnya Daftar adalah Koleksi atau Ganda adalah Angka, yang merupakan Objek.

Jika Predikat (dan semua antarmuka fungsional generik khusus lainnya, seperti Konsumen, Pemasok, IntUnaryOperator, dll.) Memiliki hubungan dengan Fungsi ini, ini akan memungkinkan seseorang untuk menggunakannya di tempat di mana parameter Fungsi diharapkan (yang terlintas dalam pikiran adalah komposisi dengan fungsi-fungsi lain, misalnya memanggil myFunction.compose (myPredicate) atau untuk menghindari penulisan beberapa fungsi khusus dalam suatu API ketika implementasi tinju otomatis (un) seperti yang dijelaskan di atas akan cukup)

EDIT 2: Melihat proyek lambda openjdk saya menemukan bahwa antarmuka fungsional primitif yang digunakan untuk memperpanjang Fungsi sampai ini berkomitmen dari Brian Goetz pada 2012-12-19. Saya tidak dapat menemukan alasan khusus untuk perubahan pada salah satu milis kelompok ahli lambda-dev atau JSR sekitar waktu itu.


35
2018-03-27 09:10


asal


Jawaban:


Metode dalam Predicate<T> kembali boolean. Metode dalam Function<T, Boolean> kembali Boolean. Mereka tidak sama. Meskipun ada autoboxing, metode Java tidak menggunakan kelas wrapper ketika primitif akan melakukannya. Juga, ada perbedaan seperti Boolean dapat null sementara boolean tidak bisa.

Ini bahkan lebih berbeda dalam kasus Consumer<T>. Metode dalam Consumer<T> memiliki tipe kembalinya void, yang berarti secara implisit dapat kembali atau kembali menggunakan return;, tetapi metode dalam Function<T, Void> harus kembali menggunakan return null; secara eksplisit.


18
2018-03-29 09:26



Tidak perlu ada hierarki warisan yang mencurigakan. Antarmuka fungsional ini dapat berubah-ubah.

Function<A,Boolean> f1=…;
Predicate<A>        p1=…;

Predicate<A>        p2=f1::apply;
Function<A,Boolean> f2=p1::test;

Ini berfungsi di kedua arah. Jadi mengapa harus ada hubungan warisan yang mengiklankan satu arah tertentu?


13
2018-04-02 14:54



Ini bukan jawaban langsung atas pertanyaan Anda, tetapi untuk apa Anda menggunakannya?

Pertimbangkan skenario berikut: Anda ingin memetakan benar / salah ke daftar nilai yang masing-masing bernilai salah.

Dengan kode Anda, Anda bisa menggunakan:

@FunctionalInterface
interface CustomPredicate<T> extends Function<T, Boolean> {
    boolean test(T value);

    @Override
    default Boolean apply(T t) {
        return test(t);
    }
}

List<String> stringList = new ArrayList<>();
stringList.add("a");
stringList.add("hg");
stringList.add("dsl");
stringList.add("sldi");
stringList.add("ilsdo");
stringList.add("jlieio");
CustomPredicate<String> customPredicate = str -> (str.length() >= 3);
Map<Boolean, List<String>> mapping = stringList.stream()
        .collect(Collectors.groupingBy(customPredicate));

Namun berikut memberitahu saya bahwa mereka pasti memikirkan sesuatu yang serupa, karena mereka menawarkan metode partisi:

List<String> stringList = new ArrayList<>();
stringList.add("a");
stringList.add("hg");
stringList.add("dsl");
stringList.add("sldi");
stringList.add("ilsdo");
stringList.add("jlieio");
Predicate<String> predicate = str -> (str.length() >= 3);
Map<Boolean, List<String>> mapping = stringList.stream()
        .collect(Collectors.partitioningBy(predicate));

Beberapa alasan yang dapat saya pikirkan adalah:

  • Itu tidak akan intuitif untuk memiliki apply() metode tersedia dalam Predicate di mana Anda hanya mengharapkan test() metode.
  • Antarmuka fungsional dirancang untuk hanya menyediakan satu jenis fungsi dasar (tidak termasuk chaining atau operasi logis), dalam situasi Anda CustomPredicate berisi dua jenis fungsi. Itu hanya akan menambah kebingungan.

6
2018-03-27 09:28



Menurutku Function<T, R> hanyalah definisi fungsi generik. Aku jatuh FunctionalInterfaces akan menerapkan Function, satu-satunya metode abstrak harus diberi nama apply(). Dalam konteks yang konkret FunctionalInterface seperti FilterFile, metode abstrak boolean accept(File pathname) adalah nama yang lebih baik dari itu Boolean apply(File).

Anotasi @FunctionalInterface sudah menandai sebuah antarmuka yang dimaksudkan untuk dapat digunakan sebagai FunctionalInterface. Tidak ada gunanya memiliki mereka semua mengimplementasikan antarmuka dasar lain kemudian memproses mereka dengan cara yang umum. Saya tidak melihat ketika Anda tidak peduli tentang semantik a FunctionalInterface sebelumnya agar tersedia untuk dihubungi apply untuk mereka semua.


6
2018-03-27 09:30