JavaRush /Java blogi /Random-UZ /Java tilidagi funktsional interfeyslar

Java tilidagi funktsional interfeyslar

Guruhda nashr etilgan
Salom! Java Syntax Pro kvestida biz lambda ifodalarini o'rganib chiqdik va ular funktsional interfeysdan funktsional usulni amalga oshirishdan boshqa narsa emasligini aytdik. Boshqacha qilib aytganda, bu qandaydir anonim (noma'lum) sinfning amalga oshirilishi, uning amalga oshirilmagan usuli. Va agar kurs ma'ruzalarida biz lambda iboralari bilan manipulyatsiyani o'rgangan bo'lsak, endi biz boshqacha aytganda, boshqa tomonni ko'rib chiqamiz: aynan mana shu interfeyslar. Java ning sakkizinchi versiyasi funktsional interfeyslarJava tilidagi funksional interfeyslar - 1 tushunchasini taqdim etdi . Bu nima? Bitta amalga oshirilmagan (mavhum) usulga ega interfeys funktsional hisoblanadi. Ko'pgina tashqi interfeyslar ushbu ta'rifga kiradi, masalan, avval muhokama qilingan interfeys . Shuningdek, biz o'zimiz yaratadigan interfeyslar, masalan: Comparator
@FunctionalInterface
public interface Converter<T, N> {
   N convert(T t);
}
Bizda interfeys mavjud, uning vazifasi bir turdagi ob'ektlarni boshqa turdagi ob'ektlarga aylantirishdir (adapter turi). Izoh @FunctionalInterfacejuda murakkab yoki muhim narsa emas, chunki uning maqsadi kompilyatorga ushbu interfeys funktsional ekanligini va bir nechta usuldan iborat bo'lmasligi kerakligini aytishdir. Agar ushbu izohli interfeys bir nechta bajarilmagan (mavhum) usullarga ega bo'lsa, kompilyator bu interfeysni o'tkazib yubormaydi, chunki u uni xato kod sifatida qabul qiladi. Ushbu izohsiz interfeyslarni funktsional deb hisoblash mumkin va ishlaydi, ammo @FunctionalInterfacebu qo'shimcha sug'urtadan boshqa narsa emas. Keling, sinfga qaytaylik Comparator. Agar siz uning kodiga (yoki hujjatlariga ) qarasangiz , unda bir nechta usullar mavjudligini ko'rishingiz mumkin. Keyin siz so'raysiz: qanday qilib uni funktsional interfeys deb hisoblash mumkin? Mavhum interfeyslar bitta usul doirasida bo'lmagan usullarga ega bo'lishi mumkin:
  • statik
Interfeyslar kontseptsiyasi ma'lum bir kod birligida hech qanday usul amalga oshirilmasligini anglatadi. Ammo Java 8 dan boshlab interfeyslarda statik va standart usullardan foydalanish mumkin bo'ldi. Statik usullar to'g'ridan-to'g'ri sinfga bog'langan va bunday usulni chaqirish uchun ushbu sinfning aniq ob'ektini talab qilmaydi. Ya'ni, bu usullar interfeys tushunchasiga mos keladi. Misol tariqasida, oldingi sinfga ob'ektni null uchun tekshirish uchun statik usulni qo'shamiz:
@FunctionalInterface
public interface Converter<T, N> {

   N convert(T t);

   static <T> boolean isNotNull(T t){
       return t != null;
   }
}
Ushbu usulni olgandan so'ng, kompilyator shikoyat qilmadi, ya'ni bizning interfeysimiz hali ham ishlaydi.
  • standart usullar
Java 8 dan oldin, agar interfeysda boshqa sinflar tomonidan meros qilib olingan usulni yaratish kerak bo'lsa, biz faqat har bir aniq sinfda amalga oshirilgan mavhum usulni yaratishimiz mumkin edi. Ammo bu usul barcha sinflar uchun bir xil bo'lsa-chi? Bunday holda , mavhum sinflar eng ko'p ishlatilgan . Ammo Java 8 dan boshlab, amalga oshirilgan usullar bilan interfeyslardan foydalanish imkoniyati mavjud - standart usullar. Interfeysni meros qilib olishda siz ushbu usullarni bekor qilishingiz yoki hamma narsani avvalgidek qoldirishingiz mumkin (standart mantiqni qoldiring). Standart usulni yaratishda biz kalit so'zni qo'shishimiz kerak - 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());
   }
}
Yana shuni ko'ramizki, kompilyator shikoyat qilishni boshlamagan va biz funktsional interfeys cheklovlaridan tashqariga chiqmadik.
  • Obyekt klassi usullari
Ob'ektlarni solishtirish ma'ruzasida biz barcha sinflar sinfdan meros bo'lib qolganligi haqida gapirdik Object. Bu interfeyslarga taalluqli emas. Ammo interfeysda sinfning ba'zi bir usuli bilan imzoga mos keladigan mavhum usul mavjud bo'lsa Object, bunday usul (yoki usullar) bizning funktsional interfeys cheklovimizni buzmaydi:
@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);
}
Va yana, bizning kompilyatorimiz shikoyat qilmaydi, shuning uchun interfeys Converterhali ham funktsional hisoblanadi. Endi savol tug'iladi: nima uchun biz funktsional interfeysda o'zimizni bitta amalga oshirilmagan usul bilan cheklashimiz kerak? Va keyin biz uni lambdalar yordamida amalga oshirishimiz uchun. Keling, buni misol bilan ko'rib chiqaylik Converter. Buning uchun sinf yaratamiz 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;
  }
}
Va shunga o'xshash Raccoon(rakun):
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;
  }
}
Faraz qilaylik, bizda ob'ekt bor Dogva biz uning maydonlari asosida ob'ekt yaratishimiz kerak Raccoon. Ya'ni, Converteru bir turdagi ob'ektni boshqasiga aylantiradi. Bu qanday ko'rinishga ega bo'ladi:
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);
}
Uni ishga tushirganimizda konsolga quyidagi natijani olamiz:

Raccoon has parameters: name - Bobbbie, age - 5, weight - 3
Va bu bizning usulimiz to'g'ri ishlaganligini anglatadi.Java tilidagi funksional interfeyslar - 2

Asosiy Java 8 Funktsional interfeyslari

Xo'sh, endi Java 8 bizga olib kelgan va Stream API bilan birgalikda faol ishlatiladigan bir nechta funktsional interfeyslarni ko'rib chiqaylik.

Predikat

Predicate— muayyan shart bajarilganligini tekshirish uchun funksional interfeys. Agar shart bajarilsa, qaytariladi true, aks holda - false:
@FunctionalInterface
public interface Predicate<T> {
   boolean test(T t);
}
Misol sifatida, Predicatebir qator turdagi paritetni tekshiradigan ni yaratishni ko'rib chiqing 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 chiqishi:

true
false

Iste'molchi

Consumer(ingliz tilidan - "iste'molchi") - T tipidagi ob'ektni kirish argumenti sifatida qabul qiladigan, ba'zi harakatlarni bajaradigan, lekin hech narsa qaytarmaydigan funktsional interfeys:
@FunctionalInterface
public interface Consumer<T> {
   void accept(T t);
}
Misol sifatida, topshirilgan qator argumenti bilan konsolga salomlashish vazifasini ko'rib chiqing: Consumer
public static void main(String[] args) {
   Consumer<String> greetings = x -> System.out.println("Hello " + x + " !!!");
   greetings.accept("Elena");
}
Konsol chiqishi:

Hello Elena !!!

Yetkazib beruvchi

Supplier(ingliz tilidan - provayder) - hech qanday argumentlarni qabul qilmaydigan, lekin T tipidagi ob'ektni qaytaradigan funktsional interfeys:
@FunctionalInterface
public interface Supplier<T> {
   T get();
}
Misol sifatida, Supplierro'yxatdagi tasodifiy nomlarni keltirib chiqaradigan ni ko'rib chiqing:
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());
}
Va agar biz buni ishga tushirsak, konsoldagi nomlar ro'yxatidan tasodifiy natijalarni ko'ramiz.

Funktsiya

Function— bu funksional interfeys T argumentini oladi va uni R tipidagi obyektga yuboradi, natijada u qaytariladi:
@FunctionalInterface
public interface Function<T, R> {
   R apply(T t);
}
Misol sifatida raqamlarni satr formatidan ( ) raqam formatiga ( ) o'zgartiradigan ni olaylik : FunctionStringInteger
public static void main(String[] args) {
   Function<String, Integer> valueConverter = x -> Integer.valueOf(x);
   System.out.println(valueConverter.apply("678"));
}
Uni ishga tushirganimizda konsolga quyidagi natijani olamiz:

678
PS: agar biz faqat raqamlarni emas, balki boshqa belgilarni ham satrga o'tkazsak, istisno tashlanadi - NumberFormatException.

UnaryOperator

UnaryOperator— T tipidagi ob'ektni parametr sifatida qabul qiluvchi, u ustida ba'zi amallarni bajaradigan va amallar natijasini bir xil T turdagi ob'ekt ko'rinishida qaytaradigan funktsional interfeys:
@FunctionalInterface
public interface UnaryOperator<T> {
   T apply(T t);
}
UnaryOperator, bu applyraqamni kvadrat qilish uchun o'z usulidan foydalanadi:
public static void main(String[] args) {
   UnaryOperator<Integer> squareValue = x -> x * x;
   System.out.println(squareValue.apply(9));
}
Konsol chiqishi:

81
Biz beshta funktsional interfeysni ko'rib chiqdik. Bu Java 8 dan boshlab biz uchun mavjud bo'lgan hamma narsa emas - bu asosiy interfeyslar. Qolgan mavjud bo'lganlar ularning murakkab analoglari. To'liq ro'yxatni rasmiy Oracle hujjatlarida topish mumkin .

Streamdagi funktsional interfeyslar

Yuqorida muhokama qilinganidek, bu funktsional interfeyslar Stream API bilan chambarchas bog'langan. Qanday qilib, deb so'rayapsizmi? Java tilidagi funksional interfeyslar - 3Va shuning uchun ko'plab usullar Streamushbu funktsional interfeyslar bilan ishlaydi. Funktsional interfeyslardan qanday foydalanish mumkinligini ko'rib chiqamiz Stream.

Predikat bilan usul

Misol uchun, sinf usulini olaylik Stream- filterbu argument sifatida qabul qilinadi Predicateva Streamfaqat shartni qondiradigan elementlarni qaytaradi Predicate. -a kontekstida bu faqat interfeys usulida foydalanilganda Streamqaytariladigan elementlardan o'tishini anglatadi . Bizning misolimiz shunday ko'rinadi , lekin quyidagi elementlar filtri uchun : truetestPredicatePredicateStream
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());
}
Natijada, ro'yxat evenNumbers{2, 4, 6, 8} elementlardan iborat bo'ladi. Va biz eslaganimizdek, collectu barcha elementlarni ma'lum bir to'plamga to'playdi: bizning holatlarimizda List.

Iste'molchi bilan ishlash usuli

StreamFunktsional interfeysdan foydalanadigan usullardan biri Consumerbu peek. ConsumerBizning misolimiz shunday ko'rinadi 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 chiqishi:

Hello Elena !!!
Hello John !!!
Hello Alex !!!
Hello Jim !!!
Hello Sara !!!
Ammo usul peekbilan ishlaganligi sababli Consumer, satrlarni o'zgartirish Streamsodir bo'lmaydi, lekin asl elementlar bilan peekqaytadi Stream: ular qanday kelgan bo'lsa, xuddi shunday. Shuning uchun ro'yxat peopleGreetings"Elena", "Jon", "Aleks", "Jim", "Sara" elementlaridan iborat bo'ladi. foreachUsulga o'xshash tez-tez ishlatiladigan usul ham mavjud peek, ammo farqi shundaki, u yakuniy - terminal.

Yetkazib beruvchi bilan usul

StreamFunktsional interfeysdan foydalanadigan usulga misol Supplierbo'lib generate, unga o'tkazilgan funktsional interfeys asosida cheksiz ketma-ketlikni hosil qiladi. SupplierKeling , konsolga beshta tasodifiy nomni chop etish uchun misolimizdan foydalanamiz :
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);
}
Va bu konsolda olingan natija:

John
Elena
Elena
Elena
Jim
limit(5)Bu erda biz usulga cheklov o'rnatish uchun usuldan foydalandik generate, aks holda dastur tasodifiy nomlarni konsolga cheksiz ravishda chop etadi.

Funktsiya bilan usul

StreamArgumentli usulning odatiy namunasi - bu bir turdagi elementlarni oladigan, ular bilan biror narsa qiladigan va ularni uzatadigan Functionusul , ammo bular allaqachon boshqa turdagi elementlar bo'lishi mumkin. in mapbilan misol qanday ko'rinishi mumkin : 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());
}
Natijada biz raqamlar ro'yxatini olamiz, lekin ichida Integer.

UnaryOperator bilan usul

Argument sifatida foydalanadigan usul sifatida UnaryOperatorsinf usulini olaylik Stream- iterate. Bu usul usulga o'xshaydi generate: u cheksiz ketma-ketlikni ham yaratadi, lekin ikkita argumentga ega:
  • birinchisi - ketma-ketlikni yaratish boshlanadigan element;
  • ikkinchisi UnaryOperator- birinchi elementdan yangi elementlarni yaratish tamoyilini ko'rsatadi.
Bizning misolimiz shunday ko'rinadi UnaryOperator, lekin usulda iterate:
public static void main(String[] args) {
   Stream.iterate(9, x -> x * x)
           .limit(4)
           .forEach(System.out::println);
}
Uni ishga tushirganimizda konsolga quyidagi natijani olamiz:

9
81
6561
43046721
Ya'ni, har bir elementimiz o'z-o'zidan ko'paytiriladi va birinchi to'rtta raqam uchun. Java tilidagi funksional interfeyslar - 4Ana xolos! Agar ushbu maqolani o'qib chiqqaningizdan so'ng Java-dagi Stream API-ni tushunish va o'zlashtirishga bir qadam yaqinlashsangiz juda yaxshi bo'lardi!
Izohlar
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION