JavaRush /Java Blogu /Random-AZ /Java-da funksional interfeyslər

Java-da funksional interfeyslər

Qrupda dərc edilmişdir
Salam! Java Syntax Pro axtarışında biz lambda ifadələrini öyrəndik və dedik ki, onlar funksional interfeysdən funksional metodun həyata keçirilməsindən başqa bir şey deyil. Başqa sözlə desək, bu, hansısa anonim (naməlum) sinfin həyata keçirilməsi, onun həyata keçirilməmiş üsuludur. Əgər kursun mühazirələrində lambda ifadələri ilə manipulyasiyalara baş vurmuşduqsa, indi, belə deyək, digər tərəfi nəzərdən keçirəcəyik: məhz bu interfeyslər. Java-nın səkkizinci versiyası funksional interfeyslərJava-da funksional interfeyslər - 1 konsepsiyasını təqdim etdi . Bu nədir? Bir həyata keçirilməmiş (mücərrəd) metodu olan interfeys funksional hesab olunur. Bir çox qutudan kənar interfeyslər bu tərifin altına düşür, məsələn, əvvəllər müzakirə olunmuş interfeys . Həm də özümüz yaratdığımız interfeyslər, məsələn: Comparator
@FunctionalInterface
public interface Converter<T, N> {
   N convert(T t);
}
Bizim bir interfeysimiz var, onun vəzifəsi bir növ obyektləri başqa bir obyektə (bir növ adapter) çevirməkdir. Annotasiya @FunctionalInterfacesuper mürəkkəb və ya vacib bir şey deyil, çünki onun məqsədi kompilyatora bu interfeysin funksional olduğunu və birdən çox metodu ehtiva etmədiyini söyləməkdir. Bu annotasiya ilə interfeys birdən çox yerinə yetirilməmiş (mücərrəd) metoda malikdirsə, kompilyator bu interfeysi atlamayacaq, çünki onu səhv kod kimi qəbul edəcəkdir. Bu annotasiya olmayan interfeyslər funksional hesab edilə bilər və işləyəcək, lakin @FunctionalInterfacebu əlavə sığortadan başqa bir şey deyil. Gəlin dərsə qayıdaq Comparator. Onun koduna (və ya sənədlərinə ) baxsanız , onun birdən çox metodu olduğunu görə bilərsiniz. Onda soruşursunuz: o zaman onu necə funksional interfeys hesab etmək olar? Abstrakt interfeyslər bir metodun əhatə dairəsinə daxil olmayan üsullara malik ola bilər:
  • statik
İnterfeys anlayışı o deməkdir ki, verilmiş kod vahidində heç bir metod tətbiq oluna bilməz. Lakin Java 8-dən başlayaraq interfeyslərdə statik və standart metodlardan istifadə etmək mümkün oldu. Statik metodlar birbaşa sinfə bağlıdır və belə bir metodu çağırmaq üçün həmin sinfin xüsusi obyektini tələb etmir. Yəni bu üsullar interfeys anlayışına ahəngdar şəkildə uyğun gəlir. Nümunə olaraq, əvvəlki sinifə obyekti null üçün yoxlamaq üçün statik metod əlavə edək:
@FunctionalInterface
public interface Converter<T, N> {

   N convert(T t);

   static <T> boolean isNotNull(T t){
       return t != null;
   }
}
Bu üsulu qəbul edən kompilyator şikayət etmədi, yəni interfeysimiz hələ də işləkdir.
  • standart üsullar
Java 8-dən əvvəl, əgər bizə digər siniflər tərəfindən miras qalmış interfeysdə metod yaratmaq lazım idisə, biz yalnız hər bir konkret sinifdə həyata keçirilən mücərrəd metod yarada bilərdik. Bəs bu üsul bütün siniflər üçün eyni olsa nə olar? Bu zaman ən çox abstrakt siniflərdən istifadə olunurdu . Ancaq Java 8-dən başlayaraq, tətbiq edilmiş metodlarla interfeyslərdən istifadə etmək imkanı var - standart üsullar. Bir interfeysi miras alarkən, siz bu üsulları ləğv edə və ya hər şeyi olduğu kimi tərk edə bilərsiniz (standart məntiqi tərk edin). Defolt metod yaratarkən biz açar söz əlavə etməliyik - default:
@FunctionalInterface
public interface Converter<T, N> {

   N convert(T t);

   static <T> boolean isNotNull(T t){
       return t != null;
   }

   default void writeToConsole(T t) {
       System.out.println("Текущий an object - " + t.toString());
   }
}
Yenə də görürük ki, kompilyator şikayət etməyə başlamadı və biz funksional interfeysin məhdudiyyətlərindən kənara çıxmadıq.
  • Obyekt sinfi metodları
Obyektlərin müqayisəsi mühazirəmizdə biz bütün siniflərin sinifdən miras qalmasından danışdıq Object. Bu interfeyslərə aid deyil. Ancaq interfeysdə sinifin bəzi metodu ilə imzaya uyğun gələn mücərrəd metodumuz varsa Object, belə bir üsul (və ya metodlar) funksional interfeys məhdudiyyətimizi pozmayacaq:
@FunctionalInterface
public interface Converter<T, N> {

   N convert(T t);

   static <T> boolean isNotNull(T t){
       return t != null;
   }

   default void writeToConsole(T t) {
       System.out.println("Текущий an object - " + t.toString());
   }

   boolean equals(Object obj);
}
Və yenə də kompilyatorumuz şikayət etmir, ona görə də interfeys Converterhələ də funksional hesab olunur. İndi sual budur: niyə funksional interfeysdə özümüzü bir həyata keçirilməmiş metodla məhdudlaşdırmalıyıq? Və sonra biz bunu lambdalardan istifadə edərək həyata keçirə bilək. Buna bir nümunə ilə baxaq Converter. Bunu etmək üçün bir sinif yaradaq Dog:
public class Dog {
  String name;
  int age;
  int weight;

  public Dog(final String name, final int age, final int weight) {
     this.name = name;
     this.age = age;
     this.weight = weight;
  }
}
Və buna bənzər Raccoon(yenot):
public class Raccoon {
  String name;
  int age;
  int weight;

  public Raccoon(final String name, final int age, final int weight) {
     this.name = name;
     this.age = age;
     this.weight = weight;
  }
}
Tutaq ki, bizim obyektimiz var Dogvə onun sahələrinə əsasən obyekt yaratmalıyıq Raccoon. Yəni Converterbir növ obyekti digərinə çevirir. Necə görünəcək:
public static void main(String[] args) {
  Dog dog = new Dog("Bobbie", 5, 3);

  Converter<Dog, Raccoon> converter = x -> new Raccoon(x.name, x.age, x.weight);

  Raccoon raccoon = converter.convert(dog);

  System.out.println("Raccoon has parameters: name - " + raccoon.name + ", age - " + raccoon.age + ", weight - " + raccoon.weight);
}
Onu işə saldığımız zaman konsola aşağıdakı çıxışı alırıq:

Raccoon has parameters: name - Bobbbie, age - 5, weight - 3
Və bu, bizim metodumuzun düzgün işlədiyini göstərir.Java-da funksional interfeyslər - 2

Əsas Java 8 Funksional İnterfeysləri

Yaxşı, indi Java 8-in bizə gətirdiyi və Stream API ilə birlikdə fəal şəkildə istifadə olunan bir neçə funksional interfeysə baxaq.

Predikat

Predicate— müəyyən şərtin yerinə yetirilib-yetirilmədiyini yoxlamaq üçün funksional interfeys. Şərt yerinə yetirilərsə, qaytarır true, əks halda - false:
@FunctionalInterface
public interface Predicate<T> {
   boolean test(T t);
}
Nümunə olaraq, Predicatebir sıra növlərin paritetini yoxlayacaq bir yaratmağı düşünün Integer:
public static void main(String[] args) {
   Predicate<Integer> isEvenNumber = x -> x % 2==0;

   System.out.println(isEvenNumber.test(4));
   System.out.println(isEvenNumber.test(3));
}
Konsol çıxışı:

true
false

İstehlakçı

Consumer(İngilis dilindən - "istehlakçı") - T tipli obyekti giriş arqumenti kimi qəbul edən, bəzi hərəkətləri yerinə yetirən, lakin heç nə qaytarmayan funksional interfeys:
@FunctionalInterface
public interface Consumer<T> {
   void accept(T t);
}
Nümunə olaraq nəzərdən keçirək ki , vəzifəsi ötürülən string arqumenti ilə konsola salam göndərməkdir: Consumer
public static void main(String[] args) {
   Consumer<String> greetings = x -> System.out.println("Hello " + x + " !!!");
   greetings.accept("Elena");
}
Konsol çıxışı:

Hello Elena !!!

Təchizatçı

Supplier(İngilis dilindən - provayder) - heç bir arqument qəbul etməyən, lakin T tipli bir obyekti qaytaran funksional interfeys:
@FunctionalInterface
public interface Supplier<T> {
   T get();
}
Nümunə olaraq, Suppliersiyahıdan təsadüfi adlar yaradacaq olanı nəzərdən keçirək:
public static void main(String[] args) {
   ArrayList<String> nameList = new ArrayList<>();
   nameList .add("Elena");
   nameList .add("John");
   nameList .add("Alex");
   nameList .add("Jim");
   nameList .add("Sara");

   Supplier<String> randomName = () -> {
       int value = (int)(Math.random() * nameList.size());
       return nameList.get(value);
   };

   System.out.println(randomName.get());
}
Və bunu işlətsək, konsoldakı adlar siyahısından təsadüfi nəticələr görəcəyik.

Funksiya

Function— bu funksional interfeys T arqumentini götürür və onu R tipli obyektə göndərir və nəticədə o, qaytarılır:
@FunctionalInterface
public interface Function<T, R> {
   R apply(T t);
}
Nümunə olaraq, rəqəmləri sətir formatından ( ) rəqəm formatına ( ) çevirən -i götürək : FunctionStringInteger
public static void main(String[] args) {
   Function<String, Integer> valueConverter = x -> Integer.valueOf(x);
   System.out.println(valueConverter.apply("678"));
}
Onu işə saldığımız zaman konsola aşağıdakı çıxışı alırıq:

678
PS: sətirə təkcə rəqəmləri deyil, digər simvolları da keçirsək, istisna atılacaq - NumberFormatException.

UnaryOperator

UnaryOperator— T tipli obyekti parametr kimi götürən, onun üzərində bəzi əməliyyatları yerinə yetirən və əməliyyatların nəticəsini eyni T tipli obyekt şəklində qaytaran funksional interfeys:
@FunctionalInterface
public interface UnaryOperator<T> {
   T apply(T t);
}
UnaryOperatorapply, ədədi kvadratlaşdırmaq üçün öz metodundan istifadə edir :
public static void main(String[] args) {
   UnaryOperator<Integer> squareValue = x -> x * x;
   System.out.println(squareValue.apply(9));
}
Konsol çıxışı:

81
Beş funksional interfeysə baxdıq. Java 8-dən başlayaraq bizim üçün mövcud olanların hamısı bu deyil - bunlar əsas interfeyslərdir. Mövcud olanların qalan hissəsi onların mürəkkəb analoqlarıdır. Tam siyahı rəsmi Oracle sənədlərində tapıla bilər .

Stream-də funksional interfeyslər

Yuxarıda müzakirə edildiyi kimi, bu funksional interfeyslər Stream API ilə sıx bağlıdır. Necə, soruşursan? Java-da funksional interfeyslər - 3Və belə ki, bir çox metodlar Streambu funksional interfeyslərlə xüsusi olaraq işləyir. Funksional interfeyslərin necə istifadə oluna biləcəyinə baxaq Stream.

Predikat ilə metod

Məsələn, arqument kimi götürən və yalnız şərti təmin edən elementləri qaytaran sinif Streammetodunu götürək . -a kontekstində bu o deməkdir ki, o, yalnız interfeys metodunda istifadə edildikdə qaytarılan elementlərdən keçir . Nümunəmiz belə görünür , ancaq elementlərin filtri üçün : filterPredicateStreamPredicateStreamtruetestPredicatePredicateStream
public static void main(String[] args) {
   List<Integer> evenNumbers = Stream.of(1, 2, 3, 4, 5, 6, 7, 8)
           .filter(x -> x % 2==0)
           .collect(Collectors.toList());
}
Nəticədə siyahı evenNumbers{2, 4, 6, 8} elementlərindən ibarət olacaq. Və xatırladığımız kimi, collectbütün elementləri müəyyən bir kolleksiyaya toplayacaq: bizim vəziyyətimizdə List.

İstehlakçı ilə metod

StreamFunksional interfeysdən istifadə edən metodlardan biri Consumerpeek. Consumerİçindəki nümunəmiz belə görünəcək Stream:
public static void main(String[] args) {
   List<String> peopleGreetings = Stream.of("Elena", "John", "Alex", "Jim", "Sara")
           .peek(x -> System.out.println("Hello " + x + " !!!"))
           .collect(Collectors.toList());
}
Konsol çıxışı:

Hello Elena !!!
Hello John !!!
Hello Alex !!!
Hello Jim !!!
Hello Sara !!!
Ancaq metod peekilə işlədiyi üçün Consumersətirlərin modifikasiyası Streambaş verməyəcək, lakin orijinal elementlərlə peekqayıdacaq Stream: necə gəldilər. Buna görə də siyahı peopleGreetings"Elena", "Con", "Aleks", "Cim", "Sara" elementlərindən ibarət olacaq. foreachÜsulun oxşarı olan çox istifadə edilən bir üsul da var peek, lakin fərq sondur - terminal.

Təchizatçı ilə üsul

StreamFunksional interfeysdən istifadə edən metodun nümunəsi ona ötürülən funksional interfeys əsasında sonsuz ardıcıllıq yaradan Supplier. Konsola beş təsadüfi adı çap etmək üçün generatenümunəmizdən istifadə edək :Supplier
public static void main(String[] args) {
   ArrayList<String> nameList = new ArrayList<>();
   nameList.add("Elena");
   nameList.add("John");
   nameList.add("Alex");
   nameList.add("Jim");
   nameList.add("Sara");

   Stream.generate(() -> {
       int value = (int) (Math.random() * nameList.size());
       return nameList.get(value);
   }).limit(5).forEach(System.out::println);
}
Və bu konsolda əldə etdiyimiz çıxışdır:

John
Elena
Elena
Elena
Jim
limit(5)Burada metoda limit təyin etmək üçün metoddan istifadə etdik generate, əks halda proqram təsadüfi adları qeyri-müəyyən müddətə konsola çap edərdi.

Funksiya ilə Metod

StreamArqumentli metodun tipik nümunəsi, bir növ elementləri götürən, onlarla bir şey edən və onları ötürən Functionbir üsuldur , lakin bunlar artıq başqa tipli elementlər ola bilər. in mapilə bir nümunə necə görünə bilər : FunctionStream
public static void main(String[] args) {
   List<Integer> values = Stream.of("32", "43", "74", "54", "3")
           .map(x -> Integer.valueOf(x)).collect(Collectors.toList());
}
Nəticədə rəqəmlərin siyahısını alırıq, lakin Integer.

UnaryOperator ilə üsul

Arqument kimi istifadə edən bir üsul olaraq UnaryOperator, bir sinif metodunu götürək Stream- iterate. Bu üsul metoda bənzəyir generate: o, həmçinin sonsuz ardıcıllıq yaradır, lakin iki arqumentə malikdir:
  • birincisi, ardıcıllığın nəslinin başladığı elementdir;
  • ikincisi UnaryOperator, birinci elementdən yeni elementlərin yaranması prinsipini göstərir.
Bizim nümunəmiz belə görünəcək UnaryOperator, lakin metodda iterate:
public static void main(String[] args) {
   Stream.iterate(9, x -> x * x)
           .limit(4)
           .forEach(System.out::println);
}
Onu işə saldığımız zaman konsola aşağıdakı çıxışı alırıq:

9
81
6561
43046721
Yəni elementlərimizin hər biri özünə vurulur və ilk dörd ədəd üçün belə davam edir. Java-da funksional interfeyslər - 4Hamısı budur! Bu məqaləni oxuduqdan sonra Java-da Stream API-ni başa düşməyə və mənimsəməyə bir addım daha yaxınlaşsanız çox yaxşı olardı!
Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION