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ər 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: Predikat
İstehlakçı
Təchizatçı
Funksiya
UnaryOperator
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 @FunctionalInterface
super 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 @FunctionalInterface
bu ə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
@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
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ı
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 Converter
hə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 Dog
və onun sahələrinə əsasən obyekt yaratmalıyıq Raccoon
. Yəni Converter
bir 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.
Ə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, Predicate
bir 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, Supplier
siyahı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 : Function
String
Integer
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);
}
UnaryOperator
apply
, ə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? Və belə ki, bir çox metodlarStream
bu 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 sinifStream
metodunu 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 : filter
Predicate
Stream
Predicate
Stream
true
test
Predicate
Predicate
Stream
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, collect
bütün elementləri müəyyən bir kolleksiyaya toplayacaq: bizim vəziyyətimizdə List
.
İstehlakçı ilə metod
Stream
Funksional interfeysdən istifadə edən metodlardan biri Consumer
də peek
. 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 peek
ilə işlədiyi üçün Consumer
sətirlərin modifikasiyası Stream
baş verməyəcək, lakin orijinal elementlərlə peek
qayı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
Stream
Funksional 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 generate
nü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
Stream
Arqumentli metodun tipik nümunəsi, bir növ elementləri götürən, onlarla bir şey edən və onları ötürən Function
bir üsuldur , lakin bunlar artıq başqa tipli elementlər ola bilər. in map
ilə bir nümunə necə görünə bilər : Function
Stream
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 olaraqUnaryOperator
, 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.
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. Hamı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ı!
GO TO FULL VERSION