Сәлеметсіз бе! Java Syntax Pro квестінде біз лямбда өрнектерін зерттедік және олар функционалды интерфейстен функционалды әдісті жүзеге асырудан басқа ештеңе емес екенін айттық. Басқаша айтқанда, бұл қандай да бір анонимді (белгісіз) классты жүзеге асыру, оның іске асырылмаған әдісі. Егер курстың лекцияларында біз лямбда өрнектерімен манипуляцияларды қарастыратын болсақ, енді біз, былайша айтқанда, екінші жағын қарастырамыз: дәл осы интерфейстер. Java сегізінші нұсқасы функционалдық интерфейстер тұжырымдамасын енгізді . Бұл не? Бір орындалмаған (абстрактілі) әдіспен интерфейс функционалды болып саналады. Көптеген дайын интерфейстер осы анықтамаға жатады, мысалы, бұрын талқыланған интерфейс . Сондай-ақ өзіміз жасайтын интерфейстер, мысалы: Предикат
Тұтынушы
Жеткізуші
Функция
UnaryOperator
Comparator
@FunctionalInterface
public interface Converter<T, N> {
N convert(T t);
}
Бізде интерфейс бар, оның міндеті бір түрдегі нысандарды басқа нысандарға (адаптер түрі) түрлендіру болып табылады. Аннотация @FunctionalInterface
өте күрделі немесе маңызды нәрсе емес, өйткені оның мақсаты компиляторға бұл интерфейстің функционалды екенін және бірнеше әдісті қамтуы керектігін айту. Егер осы annotationмен интерфейсте бірнеше орындалмаған (дерексіз) әдістер болса, компилятор бұл интерфейсті өткізіп жібермейді, өйткені ол оны қате code ретінде қабылдайды. Бұл annotationсыз интерфейстерді функционалды деп санауға болады және жұмыс істейді, бірақ @FunctionalInterface
бұл қосымша сақтандырудан басқа ештеңе емес. Сабаққа қайтайық Comparator
. Егер сіз оның codeын (немесе құжаттамасын ) қарасаңыз , оның бірнеше әдісі бар екенін көре аласыз. Сонда сіз сұрайсыз: қалай, оны функционалды интерфейс деп санауға болады? Абстрактілі интерфейстерде бір әдістің ауқымына кірмейтін әдістер болуы мүмкін:
- статикалық
@FunctionalInterface
public interface Converter<T, N> {
N convert(T t);
static <T> boolean isNotNull(T t){
return t != null;
}
}
Бұл әдісті алғаннан кейін компилятор шағымданбады, яғни біздің интерфейс әлі де жұмыс істейді.
- әдепкі әдістер
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());
}
}
Тағы да, біз компилятор шағымданбағанын көреміз және біз функционалды интерфейстің шектеулерінен шықпадық.
- Объектілер класының әдістері
Object
. Бұл интерфейстерге қолданылмайды. Бірақ интерфейсте класстың қандай да бір әдісімен қолтаңбаға сәйкес келетін дерексіз әдіс болса Object
, мұндай әдіс (немесе әдістер) біздің функционалды интерфейс шектеуімізді бұзбайды:
@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);
}
Тағы да, біздің компилятор шағымданбайды, сондықтан интерфейс Converter
әлі де функционалды болып саналады. Енді сұрақ туындайды: неге біз функционалды интерфейсте бір ғана іске асырылмаған әдіспен шектелуіміз керек? Содан кейін біз оны ламбдалар арқылы жүзеге асыра аламыз. Мұны мысалмен қарастырайық Converter
. Ол үшін класс құрайық 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;
}
}
Және ұқсас Raccoon
(енот):
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;
}
}
Бізде an object бар делік Dog
, оның өрістері негізінде an object құру керек Raccoon
. Яғни, Converter
ол бір түрдегі an objectіні екіншісіне түрлендіреді. Ол қалай көрінеді:
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);
}
Біз оны іске қосқан кезде консольге келесі нәтижені аламыз:
Raccoon has parameters: name - Bobbbie, age - 5, weight - 3
Бұл біздің әдісіміздің дұрыс жұмыс істегенін білдіреді.
Негізгі Java 8 функционалдық интерфейстері
Енді Java 8 бізге әкелген және Stream API-мен бірге белсенді қолданылатын бірнеше функционалды интерфейстерді қарастырайық.Предикат
Predicate
— белгілі бір шарттың орындалғанын тексеруге арналған функционалды интерфейс. Шарт орындалса, қайтарады true
, әйтпесе - false
:
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}
Мысал ретінде, Predicate
сан түрінің теңдігін тексеретін a жасауды қарастырыңыз 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));
}
Консоль шығысы:
true
false
Тұтынушы
Consumer
(ағылшын тілінен – «тұтынушы») – кіріс аргументі ретінде T түріндегі нысанды қабылдайтын, кейбір әрекеттерді орындайтын, бірақ ештеңені қайтармайтын функционалды интерфейс:
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}
Мысал ретінде, тапсырылған жол аргументі бар консольге сәлемдесуді шығару міндетін қарастырайық: Consumer
public static void main(String[] args) {
Consumer<String> greetings = x -> System.out.println("Hello " + x + " !!!");
greetings.accept("Elena");
}
Консоль шығысы:
Hello Elena !!!
Жеткізуші
Supplier
(ағылшын тілінен - провайдер) - ешқандай аргумент қабылдамайтын, бірақ T түріндегі нысанды қайтаратын функционалды интерфейс:
@FunctionalInterface
public interface Supplier<T> {
T get();
}
Мысал ретінде 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");
Supplier<String> randomName = () -> {
int value = (int)(Math.random() * nameList.size());
return nameList.get(value);
};
System.out.println(randomName.get());
}
Егер біз мұны іске қоссақ, консольдегі атаулар тізімінен кездейсоқ нәтижелерді көреміз.
Функция
Function
— бұл функционалды интерфейс T аргументін қабылдайды және оны R түріндегі an objectіге шығарады, нәтижесінде ол қайтарылады:
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
}
Мысал ретінде сандарды жол пішімінен ( ) сандық пішімге ( ) түрлендіретін алайық : Function
String
Integer
public static void main(String[] args) {
Function<String, Integer> valueConverter = x -> Integer.valueOf(x);
System.out.println(valueConverter.apply("678"));
}
Біз оны іске қосқан кезде консольге келесі нәтижені аламыз:
678
PS: егер жолға тек сандарды ғана емес, сонымен қатар басқа таңбаларды да жіберсек, ерекше жағдай шығарылады - NumberFormatException
.
UnaryOperator
UnaryOperator
— параметр ретінде Т типті an objectіні қабылдайтын, оған кейбір операцияларды орындайтын және сол T типті an object түріндегі операциялардың нәтижесін қайтаратын функционалды интерфейс:
@FunctionalInterface
public interface UnaryOperator<T> {
T apply(T t);
}
UnaryOperator
, ол apply
санды квадраттау үшін өз әдісін пайдаланады:
public static void main(String[] args) {
UnaryOperator<Integer> squareValue = x -> x * x;
System.out.println(squareValue.apply(9));
}
Консоль шығысы:
81
Біз бес функционалды интерфейсті қарастырдық. Бұл Java 8-ден бастап бізге қол жетімді нәрсе емес - бұл негізгі интерфейстер. Қалғандары олардың күрделі аналогтары болып табылады. Толық тізімді Oracle ресми құжаттамасынан табуға болады .
Stream ішіндегі функционалды интерфейстер
Жоғарыда талқыланғандай, бұл функционалды интерфейстер Stream API интерфейсімен тығыз байланысты. Қалай, сұрайсың ба? Және көптеген әдістерStream
осы функционалды интерфейстермен арнайы жұмыс істейді. Функционалдық интерфейстерді қалай қолдануға болатынын қарастырайық Stream
.
Предикат әдісі
Мысалы, аргумент ретінде қабылданатын және шартты қанағаттандыратын элементтерді ғана қайтаратын классStream
әдісін алайық . -a контекстінде бұл интерфейс әдісінде пайдаланылған кезде қайтарылатын элементтер арқылы ғана өтетінін білдіреді . Бұл біздің мысалға ұқсайды , бірақ элементтер сүзгісі үшін : 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());
}
Нәтижесінде тізім evenNumbers
{2, 4, 6, 8} элементтерінен тұрады. Және, есімізде, collect
ол барлық элементтерді белгілі бір жинаққа жинайды: біздің жағдайда, ішіне List
.
Тұтынушымен әдіс
Stream
Функционалдық интерфейсті пайдаланатын әдістердің бірі Consumer
болып табылады peek
. Біздің мысалымыз келесідей Consumer
болады 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());
}
Консоль шығысы:
Hello Elena !!!
Hello John !!!
Hello Alex !!!
Hello Jim !!!
Hello Sara !!!
Бірақ әдіс -мен peek
жұмыс істейтіндіктен Consumer
, ішіндегі жолдардың модификациясы Stream
болмайды, бірақ бастапқы элементтермен peek
қайтарылады Stream
: олар оған келген сияқты. Сондықтан тізім peopleGreetings
«Елена», «Джон», «Алекс», «Джим», «Сара» элементтерінен тұрады. Сондай-ақ жиі қолданылатын әдіс бар foreach
, ол әдіске ұқсас peek
, бірақ оның айырмашылығы соңғы болып табылады - терминал.
Жеткізушімен әдіс
Stream
Функционалдық интерфейсті пайдаланатын әдістің мысалы Supplier
ретінде generate
оған берілген функционалды интерфейс негізінде шексіз реттілік жасайды. 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);
}
Бұл консольде алатын нәтиже:
John
Elena
Elena
Elena
Jim
limit(5)
Мұнда біз әдіске шектеу қою үшін әдісті қолдандық generate
, әйтпесе бағдарлама кездейсоқ атауларды консольге шексіз басып шығарады.
Функциясы бар әдіс
Stream
Аргументі бар әдістің типтік мысалы - бір түрдегі элементтерді алатын, олармен бірдеңе жасайтын және оларды беретін Function
әдіс , бірақ бұл басқа түрдегі элементтер болуы мүмкін. in map
көмегімен мысал қандай болуы мүмкін : 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());
}
Нәтижесінде біз сандар тізімін аламыз, бірақ ішінде Integer
.
UnaryOperator көмегімен әдіс
Аргумент ретінде қолданылатын әдіс ретіндеUnaryOperator
класс әдісін алайық Stream
- iterate
. Бұл әдіс әдіске ұқсас generate
: ол сонымен қатар шексіз реттілік жасайды, бірақ екі аргументі бар:
- біріншісі – реттілік генерациясы басталатын элемент;
- екіншісі
UnaryOperator
- бірінші элементтен жаңа элементтерді құру принципін көрсетеді.
UnaryOperator
, бірақ әдісте iterate
:
public static void main(String[] args) {
Stream.iterate(9, x -> x * x)
.limit(4)
.forEach(System.out::println);
}
Біз оны іске қосқан кезде консольге келесі нәтижені аламыз:
9
81
6561
43046721
Яғни, біздің әрбір элементіміз өзіне көбейтіледі және алғашқы төрт сан үшін осылай болады. Бар болғаны! Осы мақаланы оқығаннан кейін Java тіліндегі Stream API интерфейсін түсінуге және меңгеруге бір қадам жақындасаңыз, тамаша болар еді!
GO TO FULL VERSION