Làm thế nào để phủ định một vị từ tham chiếu phương thức


330

Trong Java 8, bạn có thể sử dụng tham chiếu phương thức để lọc một luồng, ví dụ:

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

Có cách nào để tạo một tham chiếu phương thức đó là phủ định của một phương thức hiện có, tức là một cái gì đó như:

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

Tôi có thể tạo notphương thức như dưới đây nhưng tôi đã tự hỏi nếu JDK cung cấp một cái gì đó tương tự.

static <T> Predicate<T> not(Predicate<T> p) { return o -> !p.test(o); }

6
JDK-8050818 bao gồm việc bổ sung một Predicate.not(Predicate)phương thức tĩnh . Nhưng vấn đề đó vẫn còn mở nên chúng ta sẽ thấy vấn đề này sớm nhất trong Java 12 (nếu có).
Stefan Zobel

1
Có vẻ như câu trả lời này cũng có thể là giải pháp tối ưu được điều chỉnh trong JDK / 11.
Naman

2
Tôi thực sự muốn thấy một cú pháp tham chiếu phương thức đặc biệt cho trường hợp này: s.filter (String ::! IsEmpty)
Mike Twain

Câu trả lời:


176

Predicate.not( … )

cung cấp một phương thức mới Vị ngữ # không

Vì vậy, bạn có thể phủ nhận tham chiếu phương thức:

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

214

Tôi dự định nhập tĩnh sau đây để cho phép tham chiếu phương thức được sử dụng nội tuyến:

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

ví dụ

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

Cập nhật : Bắt đầu từ Java-11, JDK cũng cung cấp một giải pháp tương tự được tích hợp sẵn.


9
@SaintHill nhưng sau đó bạn phải viết nó ra, đặt tên cho tham số
flup



149

Có một cách để soạn một tham chiếu phương thức ngược lại với tham chiếu phương thức hiện tại. Xem câu trả lời của @ vlasec dưới đây cho thấy cách bằng cách chuyển rõ ràng tham chiếu phương thức sang a Predicatevà sau đó chuyển đổi nó bằng negatehàm. Đó là một cách trong số một vài cách khác không quá rắc rối để làm điều đó.

Trái ngược với điều này:

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

có phải đây là:

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

hoặc này:

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

Cá nhân, tôi thích kỹ thuật sau này vì tôi thấy nó rõ ràng hơn để đọc it -> !it.isEmpty()hơn một dàn diễn viên dài dòng rõ ràng và sau đó phủ nhận.

Người ta cũng có thể tạo một vị ngữ và sử dụng lại nó:

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

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

Hoặc, nếu có một bộ sưu tập hoặc mảng, chỉ cần sử dụng một vòng lặp for đơn giản, có ít chi phí hơn và * có thể nhanh hơn **:

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

* Nếu bạn muốn biết cái gì nhanh hơn, thì hãy sử dụng JMH http://openjdk.java.net/projects/code-tools/jmh và tránh mã điểm chuẩn tay trừ khi nó tránh được tất cả các tối ưu hóa JVM - xem Java 8: hiệu suất của Luồng vs Bộ sưu tập

** Tôi nhận được thông báo rằng kỹ thuật for-loop nhanh hơn. Nó loại bỏ việc tạo luồng, nó loại bỏ bằng cách sử dụng một lệnh gọi phương thức khác (hàm phủ định cho vị ngữ) và loại bỏ một danh sách / bộ đếm tích lũy tạm thời. Vì vậy, một vài thứ được lưu bởi cấu trúc cuối cùng có thể làm cho nó nhanh hơn.

Tôi nghĩ rằng nó đơn giản và đẹp hơn mặc dù không nhanh hơn. Nếu công việc yêu cầu búa và đinh, đừng mang cưa và keo! Tôi biết một số bạn có vấn đề với điều đó.

wish-list: Tôi muốn thấy các Streamhàm Java phát triển một chút khi người dùng Java đã quen thuộc hơn với chúng. Ví dụ: phương pháp 'đếm' trong Luồng có thể chấp nhận Predicateđể có thể thực hiện trực tiếp như thế này:

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

or

List<String> list = ...;
int notEmptyStrings = lists.count(it -> !it.isEmpty());

Tại sao bạn nói nó nhanh hơn rất nhiều ?
Jose Andias

@ JoséAndias (1) Nó nhanh hơn hay 'nhanh hơn rất nhiều'? (2) Nếu vậy, tại sao? Bạn đã xác định điều gì?
Điều phối viên

3
Tôi đang yêu cầu bạn giải thích "chạy nhanh hơn rất nhiều". Các câu hỏi: (1) Nó nhanh hơn hay 'nhanh hơn rất nhiều'? (2) Nếu vậy, tại sao? Bạn đã xác định điều gì? được trả lời tốt hơn bởi bạn, tác giả của tuyên bố. Tôi không xem nó là nhanh hơn hay chậm hơn. Cảm ơn bạn
Jose Andias

2
Sau đó, tôi sẽ loại bỏ điều này để bạn xem xét - Nó loại bỏ việc tạo luồng, nó loại bỏ bằng cách sử dụng một cuộc gọi phương thức khác (hàm phủ định cho vị ngữ) và loại bỏ danh sách / bộ đếm tích lũy tạm thời. Vì vậy, một vài điều được lưu bởi cấu trúc cuối cùng. Tôi không chắc nó nhanh hơn hay nhanh hơn bao nhiêu, nhưng tôi cho rằng nó nhanh hơn rất nhiều. Nhưng có lẽ "rất nhiều" là chủ quan. Việc mã hóa muộn hơn đơn giản hơn là tạo các vị từ và luồng âm để thực hiện đếm thẳng. Quyền của tôi .
Điều phối viên

4
phủ định () có vẻ như là một giải pháp lý tưởng. Đáng tiếc nó không tĩnh như Predicate.negate(String::isEmpty);không có đúc rườm rà.
Joel Shemtov 15/03/18

91

Predicatecó phương pháp and, ornegate.

Tuy nhiên, String::isEmptykhông phải là Predicate, nó chỉ là String -> Booleanlambda và nó vẫn có thể trở thành bất cứ thứ gì, vd Function<String, Boolean>. Gõ suy luận là những gì cần phải xảy ra đầu tiên. Các filterphương thức infers loại ngầm . Nhưng nếu bạn phủ nhận nó trước khi vượt qua nó như một đối số, nó không còn xảy ra nữa. Như @axtavt đã đề cập, suy luận rõ ràng có thể được sử dụng như một cách xấu xí:

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

Có những cách khác được khuyên trong các câu trả lời khác, với notphương pháp tĩnh và lambda rất có thể là những ý tưởng tốt nhất. Điều này kết luận phần tl; dr .


Tuy nhiên, nếu bạn muốn hiểu sâu hơn về suy luận kiểu lambda, tôi muốn giải thích thêm một chút về chiều sâu, sử dụng các ví dụ. Nhìn vào những điều này và cố gắng tìm hiểu những gì xảy ra:

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 không biên dịch - lambdas cần suy ra một giao diện chức năng (= với một phương thức trừu tượng)
  • p1 và F1 hoạt động tốt, mỗi loại suy ra một loại khác nhau
  • obj2 phôi một Predicateđến Object- ngớ ngẩn nhưng hợp lệ
  • f2 thất bại trong thời gian chạy - bạn không thể truyền Predicatetới Function, nó không còn là suy luận nữa
  • f3 hoạt động - bạn gọi phương thức của vị ngữ testđược xác định bởi lambda của nó
  • p2 không biên dịch - Integerkhông có isEmptyphương thức
  • p3 cũng không biên dịch - không có String::isEmptyphương thức tĩnh với Integerđối số

Tôi hy vọng điều này sẽ giúp hiểu rõ hơn về cách thức hoạt động của suy luận.


45

Dựa trên câu trả lời và kinh nghiệm cá nhân của người khác:

Predicate<String> blank = String::isEmpty;
content.stream()
       .filter(blank.negate())

4
Thú vị - bạn không thể nội tuyến ::tham chiếu chức năng như người ta có thể muốn ( String::isEmpty.negate()), nhưng nếu bạn gán cho một biến trước (hoặc chuyển sang Predicate<String>đầu tiên), thì nó hoạt động. Tôi nghĩ rằng lambda !sẽ dễ đọc nhất trong hầu hết các trường hợp, nhưng thật hữu ích khi biết những gì có thể và không thể biên dịch.
Joshua Goldberg

2
@JoshuaGoldberg Tôi đã giải thích rằng trong câu trả lời của mình: Tham chiếu phương thức không phải là Vị ngữ riêng. Ở đây, việc đúc được thực hiện bởi các biến.
Vlasec

17

Một lựa chọn khác là sử dụng đúc lambda trong bối cảnh không mơ hồ vào một lớp:

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

}

... và sau đó nhập tĩnh lớp tiện ích:

stream.filter(as(String::isEmpty).negate())

1
Tôi thực sự ngạc nhiên khi điều này hoạt động - nhưng có vẻ như JDK ủng hộ Dự đoán <T> hơn chức năng <T, Boolean>. Nhưng bạn sẽ không khiến Lambdas chuyển bất cứ thứ gì sang Hàm <T, Boolean>.
Vlasec

Nó hoạt động cho String nhưng không phải cho List: Error: (20, 39) java: tham chiếu đến as là mơ hồ cả hai phương thức <T> as (java.util.feft.Consumer <T>) trong com.strands.sbs.feft. Lambdas và phương thức <T, R> as (java.util.feft.Feft <T, R>) trong com.strands.sbs.feft.Lambdas khớp
Daniel Pinyol

Daniel, điều đó có thể xảy ra nếu bạn đang cố gắng sử dụng phương pháp quá tải :)
Askar Kalykov

Bây giờ tôi hiểu loại suy luận tốt hơn nhiều so với ban đầu, tôi hiểu cách thức hoạt động của nó. Về cơ bản, nó chỉ tìm thấy tùy chọn duy nhất hoạt động. Có vẻ thú vị, tôi chỉ không biết nếu có một cái tên tốt hơn mà không gây ra nồi hơi.
Vlasec

12

Không nên Predicate#negatelà những gì bạn đang tìm kiếm?


Bạn cần phải có được một Predicateđầu tiên.
Sotirios Delimanolis

21
Bạn phải chọn String::isEmpty()đến Predicate<String>trước - nó rất xấu.
axtavt

3
@assylias Sử dụng như Predicate<String> p = (Predicate<String>) String::isEmpty;p.negate().
Sotirios Delimanolis

8
@SotiriosDelimanolis Tôi biết, nhưng điều đó đánh bại mục đích - Tôi muốn viết s -> !s.isEmpty()trong trường hợp đó!
assylias

@assylias: Vâng, tôi tin rằng đó thực sự là ý tưởng; rằng chỉ cần viết ra lambda longhand là dự phòng.
Louis Wasserman

8

Trong trường hợp này bạn có thể sử dụng org.apache.commons.lang3.StringUtilsvà làm

int nonEmptyStrings = s.filter(StringUtils::isNotEmpty).count();

6
Câu hỏi là làm thế nào để phủ định bất kỳ tham chiếu phương thức nào và lấy String::isEmptylàm ví dụ. Nó vẫn là thông tin liên quan nếu bạn có trường hợp sử dụng này, nhưng nếu nó chỉ trả lời trường hợp sử dụng Chuỗi, thì nó không được chấp nhận.
Anthony Drogon

4

Tôi đã viết một lớp tiện ích hoàn chỉnh (lấy cảm hứng từ đề xuất của Askar) có thể lấy biểu thức lambda Java 8 và biến chúng (nếu có thể) thành bất kỳ lambda Java 8 tiêu chuẩn nào được định nghĩa trong gói java.util.function. Bạn có thể lấy ví dụ:

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

Bởi vì sẽ có nhiều sự mơ hồ nếu tất cả các phương thức tĩnh chỉ được đặt tên as(), tôi đã chọn gọi phương thức "là" theo sau là kiểu trả về. Điều này cho chúng ta toàn quyền kiểm soát diễn giải lambda. Dưới đây là phần đầu tiên của lớp tiện ích (hơi lớn) tiết lộ mẫu được sử dụng.

Có một cái nhìn đầy đủ về lớp học ở đây (tại ý chính).

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

4

Bạn có thể sử dụng Vị ngữ từ Bộ sưu tập Eclipse

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

Nếu bạn không thể thay đổi chuỗi từ List:

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

Nếu bạn chỉ cần một phủ định của String.isEmpty()bạn cũng có thể sử dụng StringPredicates.notEmpty().

Lưu ý: Tôi là người đóng góp cho Bộ sưu tập Eclipse.



0

Nếu bạn đang sử dụng Spring Boot (2.0.0+), bạn có thể sử dụng:

import org.springframework.util.StringUtils;

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

Mà không: return (str != null && !str.isEmpty());

Vì vậy, nó sẽ có hiệu ứng phủ định cần thiết cho isEmpty

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.