Các giao diện chức năng được sử dụng cho Java 8 là gì?


154

Tôi đã bắt gặp một thuật ngữ mới trong Java 8: "giao diện chức năng". Tôi chỉ có thể tìm thấy một công dụng của nó trong khi làm việc với các biểu thức lambda .

Java 8 cung cấp một số giao diện chức năng tích hợp và nếu chúng ta muốn xác định bất kỳ giao diện chức năng nào thì chúng ta có thể sử dụng @FunctionalInterfacechú thích. Nó sẽ cho phép chúng ta chỉ khai báo một phương thức duy nhất trong giao diện.

Ví dụ:

@FunctionalInterface
interface MathOperation {
    int operation(int a, int b);
}

Nó hữu ích như thế nào trong Java 8 ngoài việc chỉ làm việc với các biểu thức lambda ?

(Câu hỏi ở đây khác với câu hỏi tôi đã hỏi. Nó đang hỏi tại sao chúng ta cần các giao diện chức năng trong khi làm việc với các biểu thức lambda. Câu hỏi của tôi là: tại sao các giao diện chức năng khác lại có các biểu thức lambda?)


1
Có vẻ như duplcate liên kết này. Họ cũng nói về lý do tại sao chỉ có một phương thức trong Giao diện chức năng. stackoverflow.com/questions/33010594/
Kulbhushan Singh

1
@KulbhushanSingh Tôi đã thấy câu hỏi này trước khi đăng ... Cả hai câu hỏi đều có ý nghĩa khác nhau ...
Madhusudan

Câu trả lời:


127

@FunctionalInterfacechú thích là hữu ích để kiểm tra thời gian biên dịch mã của bạn. Bạn không thể có nhiều hơn một phương pháp bên cạnh static, defaultvà các phương pháp trừu tượng mà phương pháp ghi đè trong Objecttrong của bạn @FunctionalInterfacehoặc bất kỳ giao diện khác được sử dụng như một giao diện chức năng.

Nhưng bạn có thể sử dụng lambdas mà không cần chú thích này cũng như bạn có thể ghi đè các phương thức mà không cần @Overridechú thích.

Từ tài liệu

một giao diện chức năng có chính xác một phương thức trừu tượng. Vì các phương thức mặc định có một triển khai, chúng không trừu tượng. Nếu một giao diện tuyên bố một phương thức trừu tượng ghi đè một trong các phương thức công khai của java.lang.Object, thì điều đó cũng không được tính vào số phương thức trừu tượng của giao diện vì bất kỳ triển khai nào của giao diện sẽ có triển khai từ java.lang.Object hoặc ở nơi khác

Điều này có thể được sử dụng trong biểu thức lambda:

public interface Foo {
  public void doSomething();
}

Điều này không thể được sử dụng trong biểu thức lambda:

public interface Foo {
  public void doSomething();
  public void doSomethingElse();
}

Nhưng điều này sẽ đưa ra lỗi biên dịch :

@FunctionalInterface
public interface Foo {
  public void doSomething();
  public void doSomethingElse();
}

Chú thích '@FactoralInterface' không hợp lệ; Foo không phải là một giao diện chức năng


43
Nói chính xác hơn, bạn phải có chính xác một phương thức trừu tượng không ghi đè phương thức trong java.lang.Objectgiao diện chức năng.
Holger

9
... và nó hơi khác nhau để “không có nhiều hơn một publicphương pháp bên cạnh staticdefault” ...
Holger

4
Vẫn không hiểu bất kỳ điểm nào có nó. Tại sao bất cứ ai trên trái đất cũng kiểm tra xem có bao nhiêu phương thức giao diện của anh ấy / cô ấy. Giao diện đánh dấu vẫn có một điểm và một mục đích cụ thể. Các tài liệu và câu trả lời chỉ giải thích những gì nó làm, không phải là sử dụng như thế nào cả. Và "sử dụng" chính xác là những gì OP yêu cầu. Vì vậy, tôi sẽ không đề nghị câu trả lời này.
saran3h

1
@VNT lỗi biên dịch nhận các máy khách của giao diện này, nhưng bản thân giao diện không thể thay đổi. Với chú thích này, lỗi biên dịch nằm trên giao diện, vì vậy bạn chắc chắn rằng không ai sẽ phá vỡ các máy khách của giao diện của bạn.
Sergii Bishyr

2
Điều này cho thấy cách sử dụng chúng nhưng không giải thích được tại sao chúng ta cần chúng.
sheikh

14

Các tài liệu thực sự làm cho một sự khác biệt giữa các mục đích

Một loại chú thích thông tin được sử dụng để chỉ ra rằng một khai báo loại giao diện được dự định là một giao diện chức năng như được định nghĩa bởi Đặc tả ngôn ngữ Java.

và trường hợp sử dụng

Lưu ý rằng các phiên bản của giao diện chức năng có thể được tạo bằng biểu thức lambda, tham chiếu phương thức hoặc tham chiếu hàm tạo.

mà từ ngữ không loại trừ các trường hợp sử dụng khác nói chung. Vì mục đích chính là để chỉ ra một giao diện chức năng , nên câu hỏi thực tế của bạn tập trung vào nhóm Có các trường hợp sử dụng khác cho các giao diện chức năng khác với các biểu thức lambda và các tham chiếu phương thức / hàm tạo không?

Do giao diện chức năng là cấu trúc ngôn ngữ Java được xác định bởi Đặc tả ngôn ngữ Java, nên chỉ đặc tả đó mới có thể trả lời câu hỏi đó:

JLS §9.8. Các giao diện chức năng :

Giáo dục

Ngoài quy trình tạo giao diện thông thường bằng cách khai báo và khởi tạo một lớp (§15.9), các thể hiện của giao diện chức năng có thể được tạo bằng các biểu thức tham chiếu phương thức và biểu thức lambda (§15.13, §15.27).

Vì vậy, Đặc tả ngôn ngữ Java không nói khác, trường hợp sử dụng duy nhất được đề cập trong phần đó là việc tạo các cá thể giao diện với các biểu thức tham chiếu phương thức và biểu thức lambda. (Điều này bao gồm các tham chiếu hàm tạo vì chúng được ghi chú là một dạng của biểu thức tham chiếu phương thức trong đặc tả).

Vì vậy, trong một câu, không, không có trường hợp sử dụng nào khác cho nó trong Java 8.


Có thể chỉ yêu cầu quá nhiều hoặc không liên quan (bạn có thể chọn không trả lời), nhưng bạn sẽ đề xuất gì khi ai đó tạo ra một tiện ích public static String generateTaskId()so với làm cho nó "chức năng" hơn, người khác đã chọn viết nó như public class TaskIdSupplier implements Supplier<String>với getphương thức sử dụng thực hiện thế hệ hiện có. Có phải đó là sự lạm dụng các giao diện chức năng, đặc biệt là sử dụng lại Suppliertừ JDK tích hợp? Tái bút: Tôi không thể tìm thấy một nơi tốt hơn / Hỏi & Đáp để hỏi điều này. Hạnh phúc để di chuyển nếu bạn có thể đề nghị.
Naman

1
@Naman bạn không làm cho phương thức tiện ích trở nên hữu dụng hơn khi bạn tạo một lớp có tên TaskIdSupplier. Bây giờ, câu hỏi là tại sao bạn tạo ra lớp được đặt tên. Có những kịch bản mà một loại được đặt tên như vậy là cần thiết, ví dụ như khi bạn muốn hỗ trợ tìm kiếm việc thực hiện thông qua ServiceLoader. Không có gì sai khi để nó thực hiện Suppliersau đó. Nhưng khi bạn không cần nó, đừng tạo ra nó. Khi bạn chỉ cần một Supplier<String>, nó đã đủ để sử dụng DeclaringClass::generateTaskIdvà loại bỏ nhu cầu về một lớp rõ ràng là điểm của tính năng ngôn ngữ này.
Holger

Thành thật mà nói, tôi đã hướng tới một lời biện minh cho một khuyến nghị mà tôi đang truyền lại. Vì một số lý do tại nơi làm việc, tôi không thực sự cảm thấy rằng việc TaskIdSupplierthực hiện là đáng để nỗ lực, nhưng sau đó khái niệm ServiceLoaderhoàn toàn bỏ qua tâm trí của tôi. Gặp phải một vài câu hỏi trong các cuộc thảo luận chúng tôi đã có như việc sử dụng là gì Supplier's publictồn tại khi người ta có thể đi trước và phát triển giao diện của riêng mình? tại sao không có public static Supplier<String> TASK_ID_SUPPLIER = () ->...hằng số toàn cầu? . (1/2)
Naman

1
@Naman cách thành ngữ để biểu diễn các hàm trong Java là các phương thức và việc đánh giá các hàm đó giống hệt với việc gọi chúng. Không bao giờ nên một nhà phát triển buộc phải làm variable.genericMethodName(args)thay vì meaningfulMethodName(args). Sử dụng một loại lớp để biểu diễn một hàm, cho dù thông qua tham chiếu phương thức / biểu thức lambda hoặc một lớp được tạo thủ công, chỉ là phương tiện để truyền hàm xung quanh (trong trường hợp không có các loại hàm thực trong Java). Điều này chỉ nên được thực hiện khi cần thiết.
Holger

1
Khi bạn có một đoạn mã nhỏ chỉ được truyền xung quanh, bạn có thể tạo một biểu thức lambda gói gọn nó. Bất cứ khi nào cũng cần phải gọi nó như một phương thức (điều này bao gồm các kịch bản có nhu cầu thử nghiệm, khi đoạn mã không tầm thường), hãy tạo một phương thức được đặt tên có thể được gọi và sử dụng tham chiếu phương thức hoặc lớp biểu thức / lớp biểu thức lambda đóng gói một cuộc gọi, để chuyển nó xung quanh khi cần thiết. Các hằng số chỉ hữu ích khi bạn không tin tưởng vào hiệu quả của các biểu thức lambda hoặc các tham chiếu phương thức được nhúng trong mã của bạn, nói cách khác, chúng hầu như không bao giờ cần thiết.
Holger

12

Như những người khác đã nói, một giao diện chức năng là một giao diện hiển thị một phương thức. Nó có thể có nhiều hơn một phương thức, nhưng tất cả các phương thức khác phải có một triển khai mặc định. Lý do nó được gọi là "giao diện chức năng" là vì nó hoạt động hiệu quả như một chức năng. Vì bạn có thể truyền các giao diện dưới dạng tham số, điều đó có nghĩa là các hàm hiện là "công dân hạng nhất" như trong các ngôn ngữ lập trình chức năng. Điều này có nhiều lợi ích và bạn sẽ thấy chúng khá nhiều khi sử dụng API Stream. Tất nhiên, biểu thức lambda là sử dụng rõ ràng chính cho chúng.


10

Không có gì. Biểu thức Lambda là điểm duy nhất và duy nhất của chú thích đó.


6
Vâng, lamdbas hoạt động mà không có chú thích là tốt. Đó là một khẳng định giống như @Overrideđể cho trình biên dịch biết rằng bạn dự định viết một cái gì đó là "chức năng" (và gặp lỗi nếu bạn trượt).
Thilo

1
Đi thẳng vào vấn đề và câu trả lời đúng, mặc dù hơi ngắn. Tôi đã dành thời gian để thêm một câu trả lời công phu hơn nói cùng một câu với nhiều từ hơn
Holger

5

Một biểu thức lambda có thể được gán cho một loại giao diện chức năng, nhưng cũng có thể tham chiếu phương thức và các lớp ẩn danh.

Một điều tốt đẹp về giao diện chức năng cụ thể trong java.util.functionlà họ có thể được sáng tác để tạo ra các chức năng mới (như Function.andThenFunction.compose, Predicate.andvv) do các phương pháp mặc định tiện dụng chúng chứa.


Bạn nên giải thích về nhận xét này nhiều hơn. Điều gì về tham chiếu phương thức và các chức năng mới?
K.Nicholas

5

Một giao diện chỉ có một phương thức trừu tượng được gọi là Giao diện chức năng. Không bắt buộc phải sử dụng @FactoralInterface, nhưng cách tốt nhất là sử dụng nó với các giao diện chức năng để tránh vô tình thêm các phương thức bổ sung. Nếu giao diện được chú thích bằng chú thích @FactoralInterface và chúng tôi cố gắng có nhiều hơn một phương thức trừu tượng, nó sẽ ném lỗi trình biên dịch.

package com.akhi;
    @FunctionalInterface
    public interface FucnctionalDemo {

      void letsDoSomething();
      //void letsGo();      //invalid because another abstract method does not allow
      public String toString();    // valid because toString from Object 
      public boolean equals(Object o); //valid

      public static int sum(int a,int b)   // valid because method static
        {   
            return a+b;
        }
        public default int sub(int a,int b)   //valid because method default
        {
            return a-b;
        }
    }

3

Giao diện chức năng:

  • Được giới thiệu trong Java 8
  • Giao diện có chứa một phương thức "trừu tượng duy nhất".

Ví dụ 1:

   interface CalcArea {   // --functional interface
        double calcArea(double rad);
    }           

Ví dụ 2:

interface CalcGeometry { // --functional interface
    double calcArea(double rad);
    default double calcPeri(double rad) {
        return 0.0;
    }
}       

Ví dụ 3:

interface CalcGeometry {  // -- not functional interface
    double calcArea(double rad);
    double calcPeri(double rad);
}   

Chú thích Java8 - @FunctionalInterface

  • Kiểm tra chú thích rằng giao diện chỉ chứa một phương thức trừu tượng. Nếu không, tăng lỗi.
  • Mặc dù @FunctionalInterface bị thiếu, nó vẫn là giao diện chức năng (nếu có một phương thức trừu tượng duy nhất). Chú thích giúp tránh sai lầm.
  • Giao diện chức năng có thể có các phương thức tĩnh & mặc định bổ sung.
  • ví dụ: Lặp lại <>, So sánh <>, So sánh <>.

Các ứng dụng của Giao diện chức năng:

  • Tài liệu tham khảo phương pháp
  • Biểu thức Lambda
  • Tham khảo nhà xây dựng

Để tìm hiểu các giao diện chức năng, hãy tìm hiểu các phương thức mặc định đầu tiên trong giao diện và sau khi tìm hiểu giao diện chức năng, bạn sẽ dễ dàng hiểu được tham chiếu phương thức và biểu thức lambda


Hai ví dụ đầu tiên của bạn có từ khóa 'trừu tượng' không?
sofs1

1
@ sofs1 Các phương thức được khai báo trong các giao diện theo mặc định là cả công khai và trừu tượng. Bạn phải sử dụng từ khóa trừu tượng trong trường hợp các phương thức trong lớp trừu tượng. Tuy nhiên cũng tốt khi sử dụng từ khóa trừu tượng cho các phương thức trong giao diện. Họ đã cho phép nó tương thích với phiên bản java cũ hơn nhưng nó không được khuyến khích.
Ketan

2

Bạn có thể sử dụng lambda trong Java 8

public static void main(String[] args) {
    tentimes(inputPrm - > System.out.println(inputPrm));
    //tentimes(System.out::println);  // You can also replace lambda with static method reference
}

public static void tentimes(Consumer myFunction) {
    for (int i = 0; i < 10; i++)
        myFunction.accept("hello");
}

Để biết thêm thông tin về Java LambdasFunctionalInterfaces


1

@FunctionalInterface là một chú thích mới được phát hành với Java 8 và cung cấp các loại mục tiêu cho các biểu thức lambda và nó được sử dụng để kiểm tra thời gian biên dịch mã của bạn.

Khi bạn muốn sử dụng nó:

1- Giao diện của bạn không được có nhiều hơn một phương thức trừu tượng, nếu không sẽ xảy ra lỗi biên dịch.

1- Giao diện của bạn nên được tinh khiết, có nghĩa là giao diện chức năng được thiết kế để được thực hiện bởi các lớp học quốc tịch, exmple của tinh khiết là Comparatorgiao diện vì nó không phụ thuộc vào nhà nước thực hiện, trong trường hợp này lỗi biên dịch sẽ được đưa ra, nhưng trong nhiều trường hợp bạn sẽ không thể sử dụng lambda với loại giao diện này

Các java.util.functiongói chứa nhiều mục đích chung giao diện chức năng như Predicate, Consumer, Function, và Supplier.

Cũng xin lưu ý rằng bạn có thể sử dụng lambdas mà không cần chú thích này.


1

Bên cạnh các câu trả lời khác, tôi nghĩ lý do chính để "tại sao sử dụng Giao diện chức năng khác với trực tiếp với các biểu thức lambda" có thể liên quan đến bản chất của ngôn ngữ Java là Hướng đối tượng.

Các thuộc tính chính của biểu thức Lambda là: 1. Chúng có thể được chuyển qua khoảng 2. và chúng có thể được thực thi trong tương lai trong thời gian cụ thể (nhiều lần). Bây giờ để hỗ trợ tính năng này trong các ngôn ngữ, một số ngôn ngữ khác chỉ giải quyết vấn đề này.

Chẳng hạn, trong Java Script, một hàm (Hàm ẩn danh hoặc Hàm chữ) có thể được xử lý như một đối tượng. Vì vậy, bạn có thể tạo chúng một cách đơn giản và chúng cũng có thể được gán cho một biến và vv. Ví dụ:

var myFunction = function (...) {
    ...;
}
alert(myFunction(...));

hoặc thông qua ES6, bạn có thể sử dụng chức năng mũi tên.

const myFunction = ... => ...

Cho đến nay, các nhà thiết kế ngôn ngữ Java đã không chấp nhận xử lý các tính năng được đề cập thông qua các cách này (các kỹ thuật lập trình chức năng). Họ tin rằng ngôn ngữ Java là hướng đối tượng và do đó họ nên giải quyết vấn đề này thông qua các kỹ thuật hướng đối tượng. Họ không muốn bỏ lỡ sự đơn giản và nhất quán của ngôn ngữ Java.

Do đó, họ sử dụng các giao diện, vì khi một đối tượng của giao diện chỉ có một phương thức (ý tôi là giao diện chức năng) là cần thiết, bạn có thể thay thế nó bằng biểu thức lambda. Nhu la:

ActionListener listener = event -> ...;
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.