Tại sao negate () yêu cầu một dàn diễn viên rõ ràng để Dự đoán?


8

Tôi có một danh sách tên. Trên dòng 3, tôi đã phải đưa kết quả của biểu thức lambda sang Predicate<String>. Cuốn sách tôi đang đọc giải thích rằng việc chọn diễn viên là cần thiết để giúp trình biên dịch xác định giao diện chức năng phù hợp là gì.

Tuy nhiên, tôi không cần một dàn diễn viên như vậy ở dòng sau vì tôi không gọi negate(). Làm thế nào điều này làm cho bất kỳ sự khác biệt? Tôi hiểu rằng negate()ở đây trả về Predicate<String>, nhưng biểu thức lambda trước không làm như vậy?

List<String> names = new ArrayList<>();
//add various names here
names.removeIf(((Predicate<String>) str -> str.length() <= 5).negate()); //cast required
names.removeIf(((str -> str.length() <= 5))); //compiles without cast

1
... dàn diễn viên là cần thiết để giúp trình biên dịch xác định giao diện chức năng phù hợp là gì. trả lời nó nào. Lý do tại sao dòng tiếp theo không cần nó là vì suy luận đơn giản hơn nhiều và không được theo sau bởi một lời gọi phương thức trên giao diện được suy luận. Tôi đã học được cách sử dụng các hàm lambda liên quan đến thuốc generic, rằng chủ yếu là về suy luận trong các trường hợp như vậy và các tuyên bố rõ ràng rất an toàn, chẳng hạn nhưPredicate<String> predicate = str -> str.length() <= 5; names.removeIf(predicate.negate()); names.removeIf(predicate);
Naman

@Nathan Không nên gọi lệnh phủ định () đã cung cấp nhiều ngữ cảnh hơn nữa, khiến cho việc phân vai rõ ràng thậm chí còn ít cần thiết hơn? Cuối cùng, biểu thức lambda có thể đã đề cập đến một Hàm thay vì Vị ngữ, nhưng lệnh gọi tiếp theo để phủ định () sẽ loại bỏ bất kỳ chỗ nào cho sự mơ hồ.
K Man

Nhưng điều gì sẽ negate()được gọi nếu nó không thể suy ra loại thuộc tính lambda là một phần của biểu thức và do đó không thể xác định rằng biểu thức đó biểu thị a Predicate<String>.
Naman

Tôi không nói rằng phủ định () là cần thiết. Dòng cuối cùng của tôi rõ ràng làm việc mà không có nó. Tuy nhiên, tôi không thấy cách gọi negate () sẽ đột nhiên khiến trình biên dịch nhầm lẫn với kiểu giao diện chức năng.
K Man

Câu trả lời:


8

Nó không hoàn toàn chỉ vì bạn gọinegate() . Hãy xem phiên bản này, rất gần với phiên bản của bạn nhưng không biên dịch:

Predicate<String> predicate = str -> str.length() <= 5;
names.removeIf(predicate.negate());

Sự khác biệt giữa phiên bản này và phiên bản của bạn? Đó là về cách các biểu thức lambda có được các loại của chúng ("loại mục tiêu").

Bạn nghĩ gì về điều này?

(str -> str.length() <= 5).negate()?

Câu trả lời hiện tại của bạn là "nó gọi negate()trên Predicate<String>được đưa ra bởi biểu hiện str -> str.length() <= 5". Đúng? Nhưng đó chỉ là vì đây là những gì bạn muốn làm. Trình biên dịch không biết điều đó . Tại sao? Bởi vì nó có thể là bất cứ điều gì. Câu trả lời của riêng tôi cho câu hỏi trên có thể là "nó gọi negateloại giao diện chức năng khác của tôi ... (vâng, ví dụ sẽ hơi kỳ quái)

interface SentenceExpression {
    boolean checkGrammar();
    default SentenceExpression negate() {
        return ArtificialIntelligence.contradict(explainSentence());
    };
}

Tôi có thể sử dụng biểu thức lambda rất giống nhau names.removeIf((str -> str.length() <= 5).negate());nhưng có nghĩa str -> str.length() <= 5SentenceExpressionthay vì a Predicate<String>.

Giải thích: (str -> str.length() <= 5).negate()không làm str -> str.length() <= 5a Predicate<String>. Và đây là lý do tại sao tôi nói nó có thể là bất cứ thứ gì, kể cả giao diện chức năng của tôi ở trên.

Quay lại với Java ... Đây là lý do tại sao các biểu thức lambda có khái niệm "loại mục tiêu", định nghĩa cơ học mà theo đó một biểu thức lambda được trình biên dịch hiểu là một loại giao diện chức năng nhất định (nghĩa là cách bạn giúp trình biên dịch biết rằng biểu thức là một Predicate<String>thay vì SentenceExpressionhoặc bất cứ điều gì khác nó có thể được). Bạn có thể thấy hữu ích khi đọc qua Ý nghĩa của loại mục tiêu lambda và bối cảnh loại mục tiêu trong Java là gì? Java 8: Gõ đích

Một trong những bối cảnh trong đó các loại mục tiêu được suy ra (nếu bạn đọc câu trả lời trên các bài đăng đó) là bối cảnh gọi, trong đó bạn chuyển một biểu thức lambda làm đối số cho một tham số của loại giao diện chức năng và đó là những gì có thể áp dụng cho names.removeIf(((str -> str.length() <= 5)));: nó chỉ là biểu thức lambda được đưa ra làm đối số cho một phương thức có một Predicate<String>. Điều này không áp dụng cho câu lệnh không được biên dịch.

Vì vậy, nói cách khác ...

names.removeIf(str -> str.length() <= 5);sử dụng biểu thức lambda ở nơi mà loại đối số xác định rõ loại biểu thức lambda dự kiến ​​sẽ là gì (nghĩa là loại mục tiêu str -> str.length() <= 5rõ ràng Predicate<String>).

Tuy nhiên, (str -> str.length() <= 5).negate()không phải là biểu thức lambda, nó chỉ là một biểu thức tình cờ sử dụng biểu thức lambda. Điều này có nghĩa là str -> str.length() <= 5trong trường hợp này không nằm trong ngữ cảnh gọi xác định loại mục tiêu của biểu thức lambda (như trong trường hợp câu lệnh cuối cùng của bạn). Đúng, trình biên dịch biết rằng removeIfcần một Predicate<String>và nó biết chắc rằng toàn bộ biểu thức được truyền cho phương thức phải là một Predicate<String>, nhưng nó sẽ không cho rằng bất kỳ biểu thức lambda nào trong biểu thức đối số sẽ là Predicate<String>(ngay cả khi bạn xử lý nó như một vị ngữ bằng cách gọi negate()nó, nó có thể là bất cứ thứ gì tương thích với biểu thức lambda).

Đó là lý do tại sao gõ lambda của bạn với một dàn diễn viên rõ ràng (hoặc nói cách khác, như trong ví dụ phản biện đầu tiên tôi đưa ra) là cần thiết.


tên.remove If (((str -> str.length () <= 5))); biên dịch rõ ràng mà không có vấn đề. Tôi vẫn không hiểu cách gọi negate (), trả về một Vị ngữ, đột nhiên khiến trình biên dịch nghi ngờ rằng toàn bộ biểu thức đề cập đến một Vị ngữ.
K Man

@KMan Tôi nghĩ bạn cần thêm chi tiết về cách gõ biểu thức lambda. Tôi sẽ chỉnh sửa và thêm một liên kết.
ernest_k

Tôi đã đọc khá nhiều về biểu thức lambda. Có hay không toàn bộ biểu thức là một biểu thức lambda không liên quan, phải không? Có phủ định () không trả về Vị ngữ <Chuỗi> trong mã trên của tôi không? Tôi có thể nhầm, nhưng tôi không nghĩ là mình.
K Man

1
@KMan Có phủ định () không trả về Vị ngữ <Chuỗi> trong mã trên của tôi không? .. Không, nó không cho đến khi nó được gọiPredicate<String> là cái mà trình biên dịch đã thất bại trong lần đầu tiên do việc sử dụng negatechính nó như được giải thích trong câu trả lời này.
Naman

@KMan Tôi đã chỉnh sửa - Tôi đoán bạn cần đọc lại câu trả lời. Nói tóm lại, như Naman đang nói, nó nắm để hiểu rằng (str -> str.length() <= 5).negate()không làm cho str -> str.length() <= 5một Predicate<String>, ngay cả khi đó là những gì bạn có ý định.
ernest_k

4

Tôi không biết tại sao điều này phải khó hiểu như vậy. Điều này có thể được giải thích với 2 lý do, IMO.

  • Biểu thức Lambda là biểu thức poly .

Tôi sẽ cho bạn biết điều này có nghĩa là gì và những JLStừ xung quanh nó là gì. Nhưng về bản chất thì đây cũng giống như thuốc generic:

static class Me<T> {
    T t...
}

loại Tở đây là gì? Vâng, nó phụ thuộc. Nếu bạn làm :

Me<Integer> me = new Me<>(); // it's Integer
Me<String>  m2 = new Me<>(); // it's String

biểu thức poly được nói rằng chúng phụ thuộc vào bối cảnh nơi chúng được sử dụng. Biểu thức Lambda là như nhau. Chúng ta hãy biểu hiện lambda trong sự cô lập ở đây:

(String str) -> str.length() <= 5

Khi bạn nhìn vào nó, đây là gì? Vâng, đó là một Predicate<String>? Nhưng có thể là A Function<String, Boolean>? Hoặc có thể là chẵn MyTransformer<String, Boolean>, trong đó:

 interface MyTransformer<String, Boolean> {
     Boolean transform(String in){
         // do something here with "in"
     } 
 } 

Các lựa chọn là vô tận.

  • Trong lý thuyết .negate()gọi trực tiếp có thể là một lựa chọn.

Từ 10_000 dặm trên, bạn là chính xác: bạn đang cung cấp mà str -> str.length() <= 5đến một removeIfphương pháp, mà chỉ chấp nhận một Predicate. Không còn removeIfphương thức nào nữa , vì vậy trình biên dịch sẽ có thể "thực hiện đúng" khi bạn cung cấp phương thức đó (str -> str.length() <= 5).negate().

Vì vậy, làm thế nào điều này không hoạt động? Hãy bắt đầu với nhận xét của bạn:

Không phải lời kêu gọi phủ định () đã cung cấp nhiều ngữ cảnh hơn nữa, khiến cho việc phân vai rõ ràng thậm chí còn ít cần thiết hơn?

Có vẻ như đây là vấn đề chính bắt đầu, đây đơn giản không phải là cách javachoạt động. Nó không thể lấy toàn bộ str -> str.length() <= 5).negate(), tự nói với mình rằng đây là một Predicate<String>(vì bạn đang sử dụng nó làm đối số removeIf) và sau đó phân tách thêm phần mà không có .negate()và xem đó có phải là một Predicate<String>. javachành động ngược lại, nó cần phải biết mục tiêu để có thể biết liệu nó có hợp pháp để gọi negatehay không.

Ngoài ra, bạn cần phải phân biệt rõ ràng giữa các biểu thức polybiểu thức , nói chung. str -> str.length() <= 5).negate()là một biểu thức, str -> str.length() <= 5là một biểu thức poly.

Có thể có những ngôn ngữ mà mọi thứ được thực hiện khác đi và nơi có thể, javacđơn giản là không phải là loại ngôn ngữ đó .


1

Trong ví dụ sau

      names.removeIf(str -> str.length() <= 5); //compiles without cast

biểu thức trả về true. Nếu trong ví dụ sau mà không có diễn viên, truekhông biết gì về phương thứcnegate()

Mặt khác,

   names.removeIf(((Predicate<String>) str -> str.length() <= 5).negate()); //cast required

Biểu thức được truyền tới Predicate<String>để báo cho trình biên dịch biết nơi tìm phương thức negate. Sau đó, đó là phương pháp negate()thực sự thực hiện bằng cách sau:

   (s)->!test(s) where s is the string argument

Lưu ý rằng bạn có thể đạt được kết quả tương tự mà không cần diễn viên như sau:

    names.removeIf(str->!str.length <= 5) 
      // or
    name.removeIf(str->str.length > 5)

1

Không đi sâu vào mọi thứ, điểm mấu chốt là lambda str -> str.length() <= 5không nhất thiết phải là Predicate<String>như Eugene giải thích của .

Điều đó nói rằng, negate()là một hàm thành viên của Predicate<T>và trình biên dịch không thể sử dụng lệnh gọi negate()để giúp suy ra loại mà lambda nên được hiểu là. Ngay cả khi nó đã thử, có thể có vấn đề vì nếu nhiều lớp có thể cónegate() chức năng thì nó sẽ không biết nên chọn cái nào.

Hãy tưởng tượng rằng bạn là trình biên dịch.

  • Bạn thấy đấy str -> str.length() <= 5 . Bạn biết rằng chính lambda lấy String và trả về boolean nhưng không biết cụ thể nó đại diện cho loại nào.
  • Tiếp theo bạn sẽ thấy tham chiếu thành viên .negate()nhưng vì bạn không biết loại biểu thức ở bên trái dấu "." bạn cần báo cáo lỗi vì bạn không thể sử dụng lệnh gọi để phủ định để giúp suy ra loại.

Đã phủ định được thực hiện như là một hàm tĩnh thì mọi thứ sẽ hoạt động. Ví dụ:

public class PredicateUtils {
    public static <T> Predicate<T> negate(Predicate<T> p) {
        return p.negate();
}

sẽ cho phép bạn viết

names.removeIf(PredicateUtils.negate(str -> str.length() <= 5)); //compiles without cast

Bởi vì sự lựa chọn của hàm phủ định là không rõ ràng, trình biên dịch biết rằng nó cần diễn giải str -> str.length() <= 5như một Predicate<T>và có thể ép buộc đúng kiểu.

Tôi hy vọng rằng điều này sẽ giúp.

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.